From e0f5f9a14c4d11ea1c9fd1f7978a444333aa9993 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 12 May 2026 17:40:28 +0200 Subject: [PATCH 001/254] Amend AddRect(), AddPolyline() error detection to safely return an trigger error handling mechanism. Amend 6df50a0667a --- imgui.h | 2 +- imgui_draw.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index 9fdce397d..9a21e003d 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.8" -#define IMGUI_VERSION_NUM 19280 +#define IMGUI_VERSION_NUM 19281 #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_draw.cpp b/imgui_draw.cpp index cfea11c0c..4cc709514 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -823,7 +823,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw const bool thick_line = (thickness > _FringeScale); - IM_ASSERT((flags & ImDrawFlags_InvalidMask_) == 0 && "Incorrect parameter. Did you swapped 'thickness' and 'flags'?"); + IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); if (Flags & ImDrawListFlags_AntiAliasedLines) { @@ -1510,7 +1510,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'. // See "API BREAKING CHANGES" section for 1.82 and 1.90. - IM_ASSERT((flags & ImDrawFlags_InvalidMask_) == 0 && "Incorrect parameter. Did you swapped 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values + IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values if ((col & IM_COL32_A_MASK) == 0) return; From b2546a5c93090c70610046fe547b946ee3d8d986 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 13 May 2026 12:46:07 +0200 Subject: [PATCH 002/254] Comments on not needing to use ImDrawFlags_RoundCornersAll. --- docs/CHANGELOG.txt | 1 + imgui.h | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 83f19a88a..e66a79ad5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,7 @@ Breaking Changes: The new order is also more convenient as `flags` are less frequently used than `thickness` in real code. - As a general policy in Dear ImGui, all our flags default to 0 so ImDrawFlags_None was likely written 0 in some call sites. - Consider adding `#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS` in your imconfig.h, even temporarily, to clean up legacy code. + - Reminder: you do NOT need to specify `ImDrawFlags_RoundCornersAll` for rounding, it is already the default! - DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, which is part of our new standard draw callbacks. (#9378) Redirecting the earlier value into the later one when set, so both old and new code should work. diff --git a/imgui.h b/imgui.h index 9a21e003d..a978ab735 100644 --- a/imgui.h +++ b/imgui.h @@ -3245,19 +3245,23 @@ struct ImDrawListSplitter enum ImDrawFlags_ { ImDrawFlags_None = 0, - ImDrawFlags_RoundCornersTopLeft = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01. - ImDrawFlags_RoundCornersTopRight = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02. - ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04. - ImDrawFlags_RoundCornersBottomRight = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08. - ImDrawFlags_RoundCornersNone = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag! - ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) + + // Rounding for AddRect(), AddRectFilled(), PathRect() + // - When not specified, we defaults to ImDrawFlags_RoundCornersAll! So you only need to use those flags if you want another configuration. + ImDrawFlags_RoundCornersTopLeft = 1 << 4, // Round top-left corner only (when rounding > 0.0f, we default to all corners). + ImDrawFlags_RoundCornersTopRight = 1 << 5, // Round top-right corner only (when rounding > 0.0f, we default to all corners). + ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // Round bottom-left corner only (when rounding > 0.0f, we default to all corners). + ImDrawFlags_RoundCornersBottomRight = 1 << 7, // Round bottom-right corner only (when rounding > 0.0f, we default to all corners). + ImDrawFlags_RoundCornersNone = 1 << 8, // Disable rounding even if `float rounding > 0.0f`. This is NOT zero, NOT an implicit flag! + ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, // (Default!!) + ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified! ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, - ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, - ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified. ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, + + ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. ImDrawFlags_InvalidMask_ = (ImDrawFlags)0x8000000F, }; From 310c719a1fb6fce7c117f84aaa1075c2d9b20b35 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 10:48:24 +0200 Subject: [PATCH 003/254] Rework definition of ImDrawFlags_InvalidMask_ so it more strictly fits in an int32 for non C/C++ languages where it matters. (#9396, #9397) --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index a978ab735..3b7cde0fa 100644 --- a/imgui.h +++ b/imgui.h @@ -3262,7 +3262,7 @@ enum ImDrawFlags_ ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. - ImDrawFlags_InvalidMask_ = (ImDrawFlags)0x8000000F, + ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, }; // Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. From c7767926ce2bc4b6520d43849ee66f4baff68454 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 11:31:46 +0200 Subject: [PATCH 004/254] Version 1.92.9 WIP --- docs/CHANGELOG.txt | 9 +++++++++ imgui.cpp | 2 +- imgui.h | 4 ++-- imgui_demo.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 2 +- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 2 +- 8 files changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e66a79ad5..e13e2c319 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,6 +35,15 @@ HOW TO UPDATE? and API updates have been a little more frequent lately. They are documented below and in imgui.cpp and should not affect all users. - Please report any issue! +----------------------------------------------------------------------- + VERSION 1.92.9 WIP (In Progress) +----------------------------------------------------------------------- + +Breaking Changes: + +Other Changes: + + ----------------------------------------------------------------------- VERSION 1.92.8 (Released 2026-05-12) ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index 2edb22be2..04eea7f24 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (main code and documentation) // Help: diff --git a/imgui.h b/imgui.h index 3b7cde0fa..816642ed2 100644 --- a/imgui.h +++ b/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (headers) // Help: @@ -29,7 +29,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.8" +#define IMGUI_VERSION "1.92.9 WIP" #define IMGUI_VERSION_NUM 19281 #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_demo.cpp b/imgui_demo.cpp index 7d882f803..b95dd8a2b 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (demo code) // Help: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4cc709514..d3041d5f9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (drawing and font code) /* diff --git a/imgui_internal.h b/imgui_internal.h index d66b6a9db..b2d8c974a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 135410a0d..98dd7baad 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (tables and columns code) /* diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ea792f9e8..55b9e4cd8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.8 +// dear imgui, v1.92.9 WIP // (widgets code) /* From 46a050fff2d9290803a1838ad64815e306eb89f0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 13:38:32 +0200 Subject: [PATCH 005/254] Nav: minor optimization NavUpdate(). To be honest this is mostly to ease debug stepping in the common case. --- imgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 04eea7f24..01067abe7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -13797,13 +13797,13 @@ static void ImGui::NavUpdate() // FIXME-NAV: Now that keys are separated maybe we can get rid of NavInputSource? const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const ImGuiKey nav_gamepad_keys_to_change_source[] = { ImGuiKey_GamepadFaceRight, ImGuiKey_GamepadFaceLeft, ImGuiKey_GamepadFaceUp, ImGuiKey_GamepadFaceDown, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown }; - if (nav_gamepad_active) + if (nav_gamepad_active && g.NavInputSource != ImGuiInputSource_Gamepad) for (ImGuiKey key : nav_gamepad_keys_to_change_source) if (IsKeyDown(key)) g.NavInputSource = ImGuiInputSource_Gamepad; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; const ImGuiKey nav_keyboard_keys_to_change_source[] = { ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow }; - if (nav_keyboard_active) + if (nav_keyboard_active && g.NavInputSource != ImGuiInputSource_Keyboard) for (ImGuiKey key : nav_keyboard_keys_to_change_source) if (IsKeyDown(key)) g.NavInputSource = ImGuiInputSource_Keyboard; From 4088a4f40c25f143bddac3393a1f922426b379f5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 13:33:51 +0200 Subject: [PATCH 006/254] Windows: clicking on a window's empty-space to move/focus a window checks for lack of mouse button ownership. (#9382) --- docs/CHANGELOG.txt | 5 +++++ imgui.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e13e2c319..c69f33f3c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,11 @@ Breaking Changes: Other Changes: +- Windows: + - Clicking on a window's empty-space to move/focus a window checks + for lack of mouse button ownership. This gives an additional opportunity + for user code to bypass it without using a clickable item. (#9382) + ----------------------------------------------------------------------- VERSION 1.92.8 (Released 2026-05-12) diff --git a/imgui.cpp b/imgui.cpp index 01067abe7..a9677390f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5357,7 +5357,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() // Click on empty space to focus window and start moving // (after we're done with all our widgets) - if (g.IO.MouseClicked[0]) + if (IsMouseClicked(0, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner)) { // Handle the edge case of a popup being closed while clicking in its empty space. // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. @@ -5395,7 +5395,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() // With right mouse button we close popups without changing focus based on where the mouse is aimed // Instead, focus will be restored to the window under the bottom-most closed popup. // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger) - if (g.IO.MouseClicked[1] && g.HoveredId == 0) + if (g.HoveredId == 0 && IsMouseClicked(1, ImGuiInputFlags_None, ImGuiKeyOwner_NoOwner)) { // Find the top-most window between HoveredWindow and the top-most Modal Window. // This is where we can trim the popup stack. From db161b84c9866ac29ea67608a45aa9385584a8eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 13:45:03 +0200 Subject: [PATCH 007/254] Windows: clicking on a window's empty-space to move/focus a window checks for lack of queued focus request. (#9382) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c69f33f3c..abea19dc9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,8 @@ Other Changes: - Clicking on a window's empty-space to move/focus a window checks for lack of mouse button ownership. This gives an additional opportunity for user code to bypass it without using a clickable item. (#9382) + - Clicking on a window's empty-space to move/focus a window checks + for lack of queued focus request. (#9382) ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index a9677390f..c7557c8b9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5363,8 +5363,9 @@ void ImGui::UpdateMouseMovingWindowEndFrame() // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. ImGuiWindow* hovered_root = hovered_window ? hovered_window->RootWindow : NULL; const bool is_closed_popup = hovered_root && (hovered_root->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(hovered_root->PopupId, ImGuiPopupFlags_AnyPopupLevel); + const bool is_queued_focus_request = g.NavMoveSubmitted && (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi); - if (hovered_window != NULL && !is_closed_popup) + if (hovered_window != NULL && !is_closed_popup && !is_queued_focus_request) { StartMouseMovingWindow(hovered_window); //-V595 From 068e0555108d5fc93685f20ea6ee3ba599530d3d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 16:34:53 +0200 Subject: [PATCH 008/254] Textures: extract ImTextureDataUpdateNewFrame() out of ImFontAtlasUpdateNewFrame(). (#8465) --- imgui_draw.cpp | 83 ++++++++++++++++++++++++++---------------------- imgui_internal.h | 2 ++ 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d3041d5f9..dce53339b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2817,48 +2817,12 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere // Update texture status for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { + // Update and remove if requested ImTextureData* tex = atlas->TexList[tex_n]; - bool remove_from_list = false; - if (tex->Status == ImTextureStatus_OK) - { - tex->Updates.resize(0); - tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; - tex->UpdateRect.w = tex->UpdateRect.h = 0; - } if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); - // Request destroy - // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. - // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. - if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_Destroyed && tex->Status != ImTextureStatus_WantDestroy) - { - IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); - tex->Status = ImTextureStatus_WantDestroy; - } - - // If a texture has never reached the backend, they don't need to know about it. - // (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy - // when invalidating graphics objects twice, which would previously remove it from the list and crash.) - if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) - tex->Status = ImTextureStatus_Destroyed; - - // Process texture being destroyed - if (tex->Status == ImTextureStatus_Destroyed) - { - IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); - if (tex->WantDestroyNextFrame) - remove_from_list = true; // Destroy was scheduled by us - else - tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend: recreate it (e.g. freed resources mid-run) - } - - // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. - // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. - if (tex->Status == ImTextureStatus_WantDestroy) - tex->UnusedFrames++; - - // Destroy and remove + bool remove_from_list = ImTextureDataUpdateNewFrame(tex); if (remove_from_list) { IM_ASSERT(atlas->TexData != tex); @@ -2870,6 +2834,49 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere } } +bool ImTextureDataUpdateNewFrame(ImTextureData* tex) +{ + bool remove_from_list = false; + if (tex->Status == ImTextureStatus_OK) + { + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + } + + // Request destroy + // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. + // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. + if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_Destroyed && tex->Status != ImTextureStatus_WantDestroy) + { + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + } + + // If a texture has never reached the backend, they don't need to know about it. + // (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy + // when invalidating graphics objects twice, which would previously remove it from the list and crash.) + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) + tex->Status = ImTextureStatus_Destroyed; + + // Process texture being destroyed + if (tex->Status == ImTextureStatus_Destroyed) + { + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); + if (tex->WantDestroyNextFrame) + remove_from_list = true; // Destroy was scheduled by us + else + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend: recreate it (e.g. freed resources mid-run) + } + + // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. + // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. + if (tex->Status == ImTextureStatus_WantDestroy) + tex->UnusedFrames++; + + return remove_from_list; +} + void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) { IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); diff --git a/imgui_internal.h b/imgui_internal.h index b2d8c974a..70f2bad39 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1702,6 +1702,7 @@ enum ImGuiActivateFlags_ }; // Early work-in-progress API for ScrollToItem() +// FIXME: Missing flags to request making both edges visible when possible. enum ImGuiScrollFlags_ { ImGuiScrollFlags_None = 0, @@ -3979,6 +3980,7 @@ IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); +IMGUI_API bool ImTextureDataUpdateNewFrame(ImTextureData* tex); IMGUI_API void ImTextureDataQueueUpload(ImTextureData* tex, int x, int y, int w, int h); IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); From 93e396ffb75834b4fa79004a4f0fccb600a8e164 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 15 May 2026 16:38:05 +0200 Subject: [PATCH 009/254] Textures: call ImTextureDataUpdateNewFrame() for textures registered via RegisterUserTexture(). (#8789, #8465) Amend --- imgui.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index c7557c8b9..aa5fa6a18 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8976,6 +8976,8 @@ static void ImGui::UpdateTexturesNewFrame() IM_ASSERT(atlas->RendererHasTextures == has_textures); } } + for (ImTextureData* tex : g.UserTextures) + ImTextureDataUpdateNewFrame(tex); } // Build a single texture list @@ -9037,7 +9039,7 @@ ImFont* ImGui::GetDefaultFont() return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; } -// EXPERIMENTAL. Use ImTextureDataQueueUpload() to queue updates. +// EXPERIMENTAL. Use ImTextureDataQueueUpload() to queue updates. Textures logic will be automatically be updated in NewFrame(). void ImGui::RegisterUserTexture(ImTextureData* tex) { ImGuiContext& g = *GImGui; From e41d691da1aa3cd2c08bb56c058cf81c6ee31a2c Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 18 May 2026 14:13:49 +0200 Subject: [PATCH 010/254] Demo: Tree Nodes: extract 'Tree Nodes->Selectable Nodes' into its own thing. + comments (#9401) --- docs/CHANGELOG.txt | 3 ++ imgui.cpp | 11 ++--- imgui.h | 2 +- imgui_demo.cpp | 107 ++++++++++++++++++++++++++------------------- 4 files changed, 72 insertions(+), 51 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index abea19dc9..d5d77bee5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -49,6 +49,9 @@ Other Changes: for user code to bypass it without using a clickable item. (#9382) - Clicking on a window's empty-space to move/focus a window checks for lack of queued focus request. (#9382) +- Demo: + - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' + demo for clarity (manual reimplementation of basic selection). ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index aa5fa6a18..c0293949c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6318,11 +6318,12 @@ bool ImGui::IsItemToggledOpen() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; } -// Call after a Selectable() or TreeNode() involved in multi-selection. -// Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose. -// This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block. -// (Outside of multi-select, it would be misleading/ambiguous to report this signal, as widgets -// return e.g. a pressed event and user code is in charge of altering selection in ways we cannot predict.) +// Call after a Selectable() or TreeNode() items inside a BeginMultiSelect()/EndMultiSelect() scope. +// - Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose. +// Outside of a multi-select block: +// - It would be misleading/ambiguous to report this signal, as widgets return e.g. a pressed event, +// and user code is in charge of altering selection in ways we cannot predict. +// Prefer using 'if (IsItemClicked() && !IsItemToggledOpen())' for a manual reimplementation of selection. bool ImGui::IsItemToggledSelection() { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 816642ed2..b6763d426 100644 --- a/imgui.h +++ b/imgui.h @@ -837,7 +837,7 @@ namespace ImGui // Popups, Modals // - They block normal mouse hovering detection (and therefore most mouse interactions) behind them. - // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. + // - If not modal: they can be closed by clicking anywhere outside them, or by pressing Escape (call 'Shortcut(ImGuiKey_Escape)' to claim a higher-priority shortcut). // - Their visibility state (~bool) is held internally instead of being held by the programmer as we are used to with regular Begin*() calls. // - The 3 properties above are related: we need to retain popup visibility state in the library because popups may be closed as any time. // - You can bypass the hovering restriction by using ImGuiHoveredFlags_AllowWhenBlockedByPopup when calling IsItemHovered() or IsWindowHovered(). diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b95dd8a2b..673a0248a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4138,9 +4138,9 @@ static void DemoWindowWidgetsTreeNodes() { IMGUI_DEMO_MARKER("Widgets/Tree Nodes"); // See see "Examples -> Property Editor" (ShowExampleAppPropertyEditor() function) for a fancier, data-driven tree. - if (ImGui::TreeNode("Basic trees")) + if (ImGui::TreeNode("Basic Trees")) { - IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees"); + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic Trees"); for (int i = 0; i < 5; i++) { // Use SetNextItemOpen() so set the default state of a node to be open. We could @@ -4164,9 +4164,9 @@ static void DemoWindowWidgetsTreeNodes() ImGui::TreePop(); } - if (ImGui::TreeNode("Hierarchy lines")) + if (ImGui::TreeNode("Hierarchy Lines")) { - IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy lines"); + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy Lines"); static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DefaultOpen; HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags"); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesNone", &base_flags, ImGuiTreeNodeFlags_DrawLinesNone); @@ -4203,15 +4203,57 @@ static void DemoWindowWidgetsTreeNodes() ImGui::TreePop(); } - if (ImGui::TreeNode("Advanced, with Selectable nodes")) + if (ImGui::TreeNode("Selectable Nodes")) { - IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes"); + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Selectable Nodes"); HelpMarker( - "This is a more typical looking tree with selectable nodes.\n" - "Click to select, Ctrl+Click to toggle, click on arrows or double-click to open."); + "Manually implemented selectable nodes.\n" + "Click to select, Ctrl+Click to toggle, click on arrows or double-click to open.\n\n" + "You may also use the multi-select API (see 'Demo->Widgets->Selection State & Multi-Select') for more advanced multi-selection features."); + + // Hold in 'selection_mask' a simple representation of what may be user-side selection state. + // - You may retain selection state inside or outside your objects in whatever format you see fit. + // You may use ImGuiSelectionBasicStorage which is conceptually close to a set<> of identifiers. + // - We record which node was clicked and then apply selection at the end of the loop. + // - This is a manual and simplified reimplementation of multi-selection, which the full + // BeginMultiSelect() API implements better, but which is not trivial to wire for trees. + static int selection_mask = 0x00; + int node_clicked_idx = -1; + for (int node_n = 0; node_n < 6; node_n++) + { + // Disable the default "open on single-click behavior" + set Selected flag according to our selection. + // To alter selection we use if 'IsItemClicked() && !IsItemToggledOpen()', so clicking on an arrow doesn't alter selection. + // In a BeginMultiSelect()/EndMultiSelect() we could use IsItemToggledSelection() but here we reimplement and use our own logic. + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; + if (selection_mask & (1 << node_n)) + flags |= ImGuiTreeNodeFlags_Selected; + + bool is_open = ImGui::TreeNodeEx((void*)(intptr_t)node_n, flags, "Selectable Node %d", node_n); + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + node_clicked_idx = node_n; + if (is_open) + { + ImGui::BulletText(""); + ImGui::TreePop(); + } + } + if (node_clicked_idx != -1) + { + // Update selection state (process outside of tree loop to avoid visual inconsistencies during the clicking frame) + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked_idx); // Ctrl+Click to toggle + else //if (!(selection_mask & (1 << node_clicked_idx))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection + selection_mask = (1 << node_clicked_idx); // Click to single-select + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced")) + { + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced"); static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; static bool align_label_with_current_x_position = false; - static bool test_drag_and_drop = false; + static bool use_drag_and_drop = false; ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", &base_flags, ImGuiTreeNodeFlags_OpenOnArrow); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node."); @@ -4229,44 +4271,30 @@ static void DemoWindowWidgetsTreeNodes() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesToNodes", &base_flags, ImGuiTreeNodeFlags_DrawLinesToNodes); ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); - ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); - ImGui::Text("Hello!"); + ImGui::Checkbox("Make Tree Nodes as drag & drop sources", &use_drag_and_drop); if (align_label_with_current_x_position) ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); - // 'selection_mask' is dumb representation of what may be user-side selection state. - // You may retain selection state inside or outside your objects in whatever format you see fit. - // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end - /// of the loop. May be a pointer to your own node type, etc. - static int selection_mask = (1 << 2); - int node_clicked = -1; - for (int i = 0; i < 6; i++) + for (int node_n = 0; node_n < 6; node_n++) { - // Disable the default "open on single-click behavior" + set Selected flag according to our selection. - // To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection. ImGuiTreeNodeFlags node_flags = base_flags; - const bool is_selected = (selection_mask & (1 << i)) != 0; - if (is_selected) - node_flags |= ImGuiTreeNodeFlags_Selected; - if (i < 3) + if (node_n < 3) { // Items 0..2 are Tree Node - bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); - if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) - node_clicked = i; - if (test_drag_and_drop && ImGui::BeginDragDropSource()) + bool is_open = ImGui::TreeNodeEx((void*)(intptr_t)node_n, node_flags, "Selectable Node %d", node_n); + if (use_drag_and_drop && ImGui::BeginDragDropSource()) { - ImGui::SetDragDropPayload("_TREENODE", NULL, 0); + ImGui::SetDragDropPayload("MY_TREENODE_PAYLOAD_TYPE", NULL, 0); ImGui::Text("This is a drag and drop source"); ImGui::EndDragDropSource(); } - if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth)) + if (node_n == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth)) { // Item 2 has an additional inline button to help demonstrate SpanLabelWidth. ImGui::SameLine(); if (ImGui::SmallButton("button")) {} } - if (node_open) + if (is_open) { ImGui::BulletText("Blah blah\nBlah Blah"); ImGui::SameLine(); @@ -4280,26 +4308,15 @@ static void DemoWindowWidgetsTreeNodes() // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text(). node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet - ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); - if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) - node_clicked = i; - if (test_drag_and_drop && ImGui::BeginDragDropSource()) + ImGui::TreeNodeEx((void*)(intptr_t)node_n, node_flags, "Selectable Leaf %d", node_n); + if (use_drag_and_drop && ImGui::BeginDragDropSource()) { - ImGui::SetDragDropPayload("_TREENODE", NULL, 0); + ImGui::SetDragDropPayload("MY_TREENODE_PAYLOAD_TYPE", NULL, 0); ImGui::Text("This is a drag and drop source"); ImGui::EndDragDropSource(); } } } - if (node_clicked != -1) - { - // Update selection state - // (process outside of tree loop to avoid visual inconsistencies during the clicking frame) - if (ImGui::GetIO().KeyCtrl) - selection_mask ^= (1 << node_clicked); // Ctrl+Click to toggle - else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection - selection_mask = (1 << node_clicked); // Click to single-select - } if (align_label_with_current_x_position) ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); ImGui::TreePop(); From 3e7b79aa177c494481a9bfed27b530f2ec84a74b Mon Sep 17 00:00:00 2001 From: MouriNaruto Date: Tue, 19 May 2026 23:21:36 +0800 Subject: [PATCH 011/254] Backends: Win32: use SetProcessDpiAwarenessContext instead of SetThreadDpiAwarenessContext when available, to fix the OpenGL3 Win32 example DPI scaling issue. (#9403) --- backends/imgui_impl_win32.cpp | 9 ++++++++- docs/CHANGELOG.txt | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index d789f25fd..787329bf6 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-05-19: DPI: ImGui_ImplWin32_EnableDpiAwareness() helper uses SetProcessDpiAwarenessContext() instead of SetThreadDpiAwarenessContext(), fixes DPI scaling issues with e.g. OpenGL. (#9403) // 2026-01-28: Inputs: Minor optimization not submitting gamepad input if packet number has not changed (reworked from 2025-09-23 attempt). (#9202, #8556) // 2025-12-03: Inputs: handle WM_IME_CHAR/WM_IME_COMPOSITION messages to support Unicode inputs on MBCS (non-Unicode) Windows. (#9099, #3653, #5961) // 2025-10-19: Inputs: Revert previous change to allow for io.ClearInputKeys() on focus-out not losing gamepad state. @@ -886,6 +887,7 @@ DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); typedef HRESULT(WINAPI* PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); // Shcore.lib + dll, Windows 8.1+ typedef HRESULT(WINAPI* PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); // Shcore.lib + dll, Windows 8.1+ typedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1607+ (Creators Update) +typedef BOOL(WINAPI* PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1703+ (Creators Update) // Helper function to enable DPI awareness without setting up a manifest void ImGui_ImplWin32_EnableDpiAwareness() @@ -893,7 +895,12 @@ void ImGui_ImplWin32_EnableDpiAwareness() if (_IsWindows10OrGreater()) { static HINSTANCE user32_dll = ::LoadLibraryA("user32.dll"); // Reference counted per-process - if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) + if (PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContextFn = (PFN_SetProcessDpiAwarenessContext)::GetProcAddress(user32_dll, "SetProcessDpiAwarenessContext")) // Windows 10 v1703+ + { + SetProcessDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + return; + } + if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext")) // Windows 10 v1607+ { SetThreadDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); return; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d5d77bee5..59ad650bf 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -52,6 +52,11 @@ Other Changes: - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). +- Backends: + - Win32: + - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` + when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends + to spawn multiple-thread to manage OpenGL. (#9403) ----------------------------------------------------------------------- From 904b6631845d74ab1c19cb47b743c1a7122426dc Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 19 May 2026 18:50:39 +0200 Subject: [PATCH 012/254] Clarify support for "%s" shortcuts in functions taking format strings. (#9404, #3466, #6846) --- imgui.h | 3 ++- imgui_widgets.cpp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index b6763d426..787417667 100644 --- a/imgui.h +++ b/imgui.h @@ -613,7 +613,8 @@ namespace ImGui IMGUI_API ImGuiID GetID(int int_id); // Widgets: Text - IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. + // - Note that all functions taking format strings in the API may be passed ("%s", text) or ("%.*s", text_len, text): which will automatically bypass the formatter. + IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Practically equivalent to 'Text("%s", text)' but doesn't require null terminated string if 'text_end' is specified. IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // formatted text IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 55b9e4cd8..63e3f5e03 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -266,6 +266,8 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) } } +// Note that all functions taking format strings in the API may be passed ("%s", text) or ("%.*s", text_len, text), +// which will automatically bypass the formatter. void ImGui::TextUnformatted(const char* text, const char* text_end) { TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); From 24a80f74a4ec64c5bfd5bee67ac567466ffcc9a4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 22 May 2026 19:11:33 +0200 Subject: [PATCH 013/254] InputText, Style: added InputTextCursorSize to configure cursor/caret thickness. (#7031, #9409) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 2 ++ imgui.h | 1 + imgui_widgets.cpp | 2 +- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 59ad650bf..ce0f751a6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -49,6 +49,9 @@ Other Changes: for user code to bypass it without using a clickable item. (#9382) - Clicking on a window's empty-space to move/focus a window checks for lack of queued focus request. (#9382) +- InputText: + - Added style.InputTextCursorSize to configure cursor/caret thickness. (#7031, #9409) + This is automatically scaled by style.ScaleAllSizes(). - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). diff --git a/imgui.cpp b/imgui.cpp index c0293949c..25e28ed10 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1537,6 +1537,7 @@ ImGuiStyle::ImGuiStyle() ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. + InputTextCursorSize = 1.0f; // Thickness of cursor/caret in InputText(). SeparatorSize = 1.0f; // Thickness of border in Separator(). SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText(). SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). @@ -1612,6 +1613,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor); DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor); ColorMarkerSize = ImTrunc(ColorMarkerSize * scale_factor); + InputTextCursorSize = ImTrunc(InputTextCursorSize * scale_factor); SeparatorSize = ImTrunc(SeparatorSize * scale_factor); SeparatorTextBorderSize = ImTrunc(SeparatorTextBorderSize * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); diff --git a/imgui.h b/imgui.h index 787417667..1c697eb39 100644 --- a/imgui.h +++ b/imgui.h @@ -2339,6 +2339,7 @@ struct ImGuiStyle ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. + float InputTextCursorSize; // Thickness of cursor/caret in InputText(). float SeparatorSize; // Thickness of border in Separator(). Must be >= 1.0f. float SeparatorTextBorderSize; // Thickness of border in SeparatorText() ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 63e3f5e03..3807f1f00 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5643,7 +5643,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), 1.0f * (float)(int)style._MainScale); // FIXME-DPI: Cursor thickness (#7031) + draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize); // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) // This is required for some backends (SDL3) to start emitting character/text inputs. From fbcf95193f40fc57a3f0a3e8f59798de06e69bc6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 22 May 2026 20:03:30 +0200 Subject: [PATCH 014/254] ImFontAtlas: moved common TexData calls into a same helper functions, so adding new ones is easier. --- imgui_draw.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index dce53339b..c695751af 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2560,8 +2560,6 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasBuildPreloadAllGlyphRanges() // - ImFontAtlasBuildUpdatePointers() // - ImFontAtlasBuildRenderBitmapFromString() -// - ImFontAtlasBuildUpdateBasicTexData() -// - ImFontAtlasBuildUpdateLinesTexData() // - ImFontAtlasBuildAddFont() // - ImFontAtlasBuildSetupFontBakedEllipsis() // - ImFontAtlasBuildSetupFontBakedBlanks() @@ -2578,13 +2576,14 @@ void ImTextureData::DestroyPixels() // - ImFontAtlasUpdateDrawListsSharedData() //----------------------------------------------------------------------------- // - ImFontAtlasBuildSetTexture() -// - ImFontAtlasBuildAddTexture() -// - ImFontAtlasBuildMakeSpace() -// - ImFontAtlasBuildRepackTexture() -// - ImFontAtlasBuildGrowTexture() -// - ImFontAtlasBuildRepackOrGrowTexture() -// - ImFontAtlasBuildGetTextureSizeEstimate() -// - ImFontAtlasBuildCompactTexture() +// - ImFontAtlasBuildUpdateTexData() +// - ImFontAtlasTextureAdd() +// - ImFontAtlasTextureRepack() +// - ImFontAtlasTextureGrow() +// - ImFontAtlasTextureMakeSpace() +// - ImFontAtlasTextureGetSizeEstimate() +// - ImFontAtlasBuildClear() +// - ImFontAtlasTextureCompact() // - ImFontAtlasBuildInit() // - ImFontAtlasBuildDestroy() //----------------------------------------------------------------------------- @@ -3581,7 +3580,7 @@ void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, in } } -static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas) +static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas) { // Pack and store identifier so we can refresh UV coordinates on texture resize. // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. @@ -3615,7 +3614,7 @@ static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } -static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) +static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; @@ -4064,6 +4063,12 @@ static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, atlas->TexRef); } +static void ImFontAtlasBuildUpdateTexData(ImFontAtlas* atlas) +{ + ImFontAtlasBuildUpdateTexDataBasic(atlas); + ImFontAtlasBuildUpdateTexDataLines(atlas); +} + // Create a new texture, discard previous one ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h) { @@ -4178,8 +4183,7 @@ void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h) } // Update other cached UV - ImFontAtlasBuildUpdateLinesTexData(atlas); - ImFontAtlasBuildUpdateBasicTexData(atlas); + ImFontAtlasBuildUpdateTexData(atlas); builder->LockDisableResize = false; ImFontAtlasUpdateDrawListsSharedData(atlas); @@ -4328,8 +4332,7 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) ImFontAtlasPackInit(atlas); // Add required texture data - ImFontAtlasBuildUpdateLinesTexData(atlas); - ImFontAtlasBuildUpdateBasicTexData(atlas); + ImFontAtlasBuildUpdateTexData(atlas); // Register fonts ImFontAtlasBuildUpdatePointers(atlas); @@ -4358,6 +4361,8 @@ void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) atlas->Builder = NULL; } +//----------------------------------------------------------------------------- + void ImFontAtlasPackInit(ImFontAtlas * atlas) { ImTextureData* tex = atlas->TexData; From 243097ca8f46ffe361b7aca642606c3ad176c9d3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 26 May 2026 20:36:29 +0200 Subject: [PATCH 015/254] Docs: retroactively amend changelog for AddLineH(), AddLineV(). Amend 691b89b. (#9360) --- docs/CHANGELOG.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ce0f751a6..091620ec2 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -125,6 +125,15 @@ Other Changes: #5999, #6452, #5156, #7342, #7592, #7511) - Made `AddCallback()` user data default to Null for convenience. - Added `AddLineH()`, `AddLineV()` helpers to draw horizontal and vertical lines. [@memononen] + The new functions are more optimal and will be part of a larger effort in 1.93 to fix + inconsistencies and improve the DrawList API. In the current API, `AddLine()` adds a "magic" +0.5f + offset and then center the line, whereas AddLineH()/AddLineV() use the right side of the line to expand. + For thickness=1.0f lines this is equivalent: + `AddLine({10,3}, {20,3}, ...)` --> `AddLineH(x1=10, x2=20, y=3, ...)`. + `AddLine({3,10}, {3,20}, ...)` --> `AddLineV(x=3, y1=10, y2=20, ...)`. + For larger integer thickness, AddLine() will center but may be blurry, AddLineH()/AddLineV() + will expand "inside" (to the right side) and will always be sharp. + A subsequent release will expand on those concepts and add new options. - InputText: - InputTextMultiline: fixed an issue processing deactivation logic when an active multi-line edit is clipped due to being out of view. From ac07da2b5bf3c0e1b1e7ede1a8ab2e0abf6fc52a Mon Sep 17 00:00:00 2001 From: qwer <58898485+khuiqel@users.noreply.github.com> Date: Wed, 20 May 2026 21:07:56 -0700 Subject: [PATCH 016/254] Fonts: Added macros to disable ProggyClean/ProggyVector separately. (#9407) --- docs/CHANGELOG.txt | 3 +++ imconfig.h | 4 +++- imgui_draw.cpp | 20 ++++++++++++-------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 091620ec2..0cfd5458f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -52,6 +52,9 @@ Other Changes: - InputText: - Added style.InputTextCursorSize to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by style.ScaleAllSizes(). +- Fonts: + - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to + disable embedding either fonts separately. (#9407) - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). diff --git a/imconfig.h b/imconfig.h index 0d843be26..0a4ca08af 100644 --- a/imconfig.h +++ b/imconfig.h @@ -49,7 +49,9 @@ //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). -//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert. +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert. +//#define IMGUI_DISABLE_DEFAULT_FONT_BITMAP // Disable default embedded bitmap font (ProggyClean). Remove ~9 KB from output binary. AddFontDefaultBitmap() will assert. +//#define IMGUI_DISABLE_DEFAULT_FONT_VECTOR // Disable default embedded vector font (ProggyForever), Remove ~14 KB from output binary. AddFontDefaultVector() will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Enable Test Engine / Automation features. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c695751af..6cee3f467 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3149,8 +3149,10 @@ static void Decode85(const unsigned char* src, unsigned char* dst) dst += 4; } } -#ifndef IMGUI_DISABLE_DEFAULT_FONT +#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP) static const char* GetDefaultCompressedFontDataProggyClean(int* out_size); +#endif +#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR) static const char* GetDefaultCompressedFontDataProggyForever(int* out_size); #endif @@ -3175,7 +3177,7 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg) // If you want a similar font which may be better scaled, consider using AddFontDefaultVector(). ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template) { -#ifndef IMGUI_DISABLE_DEFAULT_FONT +#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP) ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (!font_cfg_template) font_cfg.PixelSnapH = true; // Prevents sub-integer scaling factors at lower-level layers. @@ -3196,14 +3198,14 @@ ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template) IM_ASSERT(0 && "Function is disabled in this build."); IM_UNUSED(font_cfg_template); return NULL; -#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT +#endif } // Load a minimal version of ProggyForever, designed to match our good old ProggyClean, but nicely scalable. // (See build script in https://github.com/ocornut/proggyforever for details) ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template) { -#ifndef IMGUI_DISABLE_DEFAULT_FONT +#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR) ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (!font_cfg_template) font_cfg.PixelSnapH = true; // Precisely match ProggyClean, but prevents sub-integer scaling factors at lower-level layers. @@ -3224,7 +3226,7 @@ ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template) IM_ASSERT(0 && "Function is disabled in this build."); IM_UNUSED(font_cfg_template); return NULL; -#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT +#endif } ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) @@ -6340,7 +6342,7 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i // Download and more information at https://github.com/bluescan/proggyfonts //----------------------------------------------------------------------------- -#ifndef IMGUI_DISABLE_DEFAULT_FONT +#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP) // File: 'ProggyClean.ttf' (41208 bytes) // Exported using binary_to_compressed_c.exe -u8 "ProggyClean.ttf" proggy_clean_ttf @@ -6520,6 +6522,7 @@ static const char* GetDefaultCompressedFontDataProggyClean(int* out_size) *out_size = proggy_clean_ttf_compressed_size; return (const char*)proggy_clean_ttf_compressed_data; } +#endif // #if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP) //----------------------------------------------------------------------------- // [SECTION] Default font data (ProggyForever-Regular-minimal.ttf) @@ -6528,6 +6531,8 @@ static const char* GetDefaultCompressedFontDataProggyClean(int* out_size) // MIT license / Copyright (c) 2026 Disco Hello, Copyright (c) 2019,2023 Tristan Grimmer //----------------------------------------------------------------------------- +#if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR) + // File: 'output/ProggyForever-Regular-minimal.ttf' (18556 bytes) // Exported using binary_to_compressed_c.exe -u8 "output/ProggyForever-Regular-minimal.ttf" proggy_forever_minimal_ttf static const unsigned int proggy_forever_minimal_ttf_compressed_size = 14562; @@ -6782,7 +6787,6 @@ static const char* GetDefaultCompressedFontDataProggyForever(int* out_size) *out_size = proggy_forever_minimal_ttf_compressed_size; return (const char*)proggy_forever_minimal_ttf_compressed_data; } - -#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT +#endif // #if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR) #endif // #ifndef IMGUI_DISABLE From 783eba926fc064b99f917020b01596bd0e201202 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 28 May 2026 11:37:37 +0200 Subject: [PATCH 017/254] Docs: retroactively amend 1.92.8 changelog about `ImDrawFlags_Closed` value. Amend 6df50a0 --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 2 ++ imgui.h | 3 +++ imgui_draw.cpp | 10 ++++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0cfd5458f..17aadb6ea 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -98,6 +98,9 @@ Breaking Changes: - DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, which is part of our new standard draw callbacks. (#9378) Redirecting the earlier value into the later one when set, so both old and new code should work. +- DrawList: changed value of `ImDrawFlags_Closed`. It was previously advertised as "always == 1" when introduced + in 1.82 (2021/02), in order to facilitate backward compatibility with the legacy `bool closed` flag. This + guarantee has been removed. The bit is reserved, and `AddPolyline()`, `PathStroke()` will assert when it is used. - Backends: - Vulkan: redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. (#914) This change allows us to facilitate changing samplers, in line with other backends. [@yaz0r, @ocornut] diff --git a/imgui.cpp b/imgui.cpp index 25e28ed10..ecd3bc1c6 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -416,6 +416,8 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: The new order is also more convenient as `flags` are less frequently used than `thickness` in real code. - As a general policy in Dear ImGui, all our flags default to 0 so ImDrawFlags_None was likely written 0 in some call sites. - Consider adding `#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS` in your imconfig.h, even temporarily, to clean up legacy code. + - 2026/05/07 (1.92.8) - DrawList: changed value of `ImDrawFlags_Closed`. It was previously advertised as "always == 1" when introduced in 1.82 (2021/02), in order to facilitate backward compatibility with the legacy `bool closed` flag. + This guarantee has been removed. The bit is reserved and `AddPolyline()`, `PathStroke()` will assert when it is used. - 2026/04/23 (1.92.8) - DrawList: obsoleted `ImDrawCallback_ResetRenderState` in favor of using `ImGui::GetPlatformIO().DrawCallback_ResetRenderState`, which is part of our new standard draw callbacks. (#9378) - 2026/04/22 (1.92.8) - Backends: Vulkan: redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. diff --git a/imgui.h b/imgui.h index 1c697eb39..cc80fc80f 100644 --- a/imgui.h +++ b/imgui.h @@ -3263,7 +3263,10 @@ enum ImDrawFlags_ ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, + // Stroke options ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. + //ImDrawFlags_Closed = 1 << 0, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. + ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6cee3f467..690b7e5b0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -823,6 +823,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw const bool thick_line = (thickness > _FringeScale); + + // If this assert triggers on legacy code: + // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. + // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); if (Flags & ImDrawListFlags_AntiAliasedLines) @@ -1503,13 +1508,14 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { // If this assert triggers on legacy code: - // - 1.92.8 (2025/04): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundCornersXXX values. // If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! // - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll. // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'. - // See "API BREAKING CHANGES" section for 1.82 and 1.90. + // See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values if ((col & IM_COL32_A_MASK) == 0) From 33bb693b4cb965e2f7a0feeee0a442814ef84cc0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 28 May 2026 15:37:29 +0200 Subject: [PATCH 018/254] DrawList: skip PathLineTo/PathStroke calls for common AddLine(), AddLineH(), AddLineV() functions. (#4091) --- docs/CHANGELOG.txt | 2 ++ imgui_draw.cpp | 15 ++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 17aadb6ea..46ee7cdc4 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,8 @@ Other Changes: - Fonts: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) +- DrawList: + - Minor optimization to AddLine(), AddLineH(), AddLineV() functions. (#4091) - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 690b7e5b0..093f0f140 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1480,27 +1480,24 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th { if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1 + ImVec2(0.5f, 0.5f)); - PathLineTo(p2 + ImVec2(0.5f, 0.5f)); - PathStroke(col, thickness); + const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; + AddPolyline(points, 2, col, thickness); } void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(ImVec2(min_x + 0.5f, y + 0.5f)); // Same as AddLine() above. - PathLineTo(ImVec2(max_x + 0.5f, y + 0.5f)); - PathStroke(col, thickness); + const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. + AddPolyline(points, 2, col, thickness); } void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(ImVec2(x + 0.5f, min_y + 0.5f)); // Same as AddLine() above. - PathLineTo(ImVec2(x + 0.5f, max_y + 0.5f)); - PathStroke(col, thickness); + const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. + AddPolyline(points, 2, col, thickness); } // p_min = upper-left, p_max = lower-right From 12b797755501ae8eced5f6fb33105d8ddc1d1cbc Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 28 May 2026 19:19:11 +0200 Subject: [PATCH 019/254] Tweak CalcTextSize() awkward width rounding/ceiling code to reduce floating-point imprecisions altering the result by 1 even at relatively small width. (#791) + apply same fudge factor to less important roundings. ceilf() is still measurable e.g. ballpark +0.5 for 200k calls. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 9 ++++----- imgui_draw.cpp | 2 +- imgui_widgets.cpp | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 46ee7cdc4..1c5535835 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,8 @@ Other Changes: - Fonts: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) + - Tweak CalcTextSize() awkward width rounding/ceiling code to reduce floating-point + imprecisions altering the result by 1 even at relatively small width. (#791) - DrawList: - Minor optimization to AddLine(), AddLineH(), AddLineV() functions. (#4091) - Demo: diff --git a/imgui.cpp b/imgui.cpp index ecd3bc1c6..735deb9d4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6199,11 +6199,10 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); // Round - // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out. - // FIXME: Investigate using ceilf or e.g. - // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c - // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html - text_size.x = IM_TRUNC(text_size.x + 0.99999f); + // FIXME: This has been here since Dec 2015 (7b0bf230 then 4622fa4b6) but down the line we want this out. See #791. + // Investigate using ceilf or e.g. https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c, https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html + // The problem is that ceilf() has a measurable cost. Not high, but measurable! + text_size.x = IM_TRUNC(text_size.x + 0.999f); return text_size; } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 093f0f140..b112f471d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -656,7 +656,7 @@ void ImDrawList::_OnChangedVtxOffset() int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const { // Automatic segment count - const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy + const int radius_idx = (int)(radius + 0.999f); // ceil to never reduce accuracy if (radius_idx >= 0 && radius_idx < IM_COUNTOF(_Data->CircleSegmentCounts)) return _Data->CircleSegmentCounts[radius_idx]; // Use cached value else diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3807f1f00..a98cb6936 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1748,14 +1748,14 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float separator_thickness = style.SeparatorTextBorderSize; const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness)); const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); - const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f)); + const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f)); ItemSize(min_size, text_baseline_y); if (!ItemAdd(bb, id)) return; const float sep1_x1 = pos.x; const float sep2_x2 = bb.Max.x; - const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f); + const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN @@ -8952,7 +8952,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get // Tooltip on hover if (hovered && inner_bb.Contains(g.IO.MousePos)) { - const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.999f); const int v_idx = (int)(t * item_count); IM_ASSERT(v_idx >= 0 && v_idx < values_count); From 75f985998b0b0759470a46cd02e73d1c675487fa Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 May 2026 15:59:44 +0200 Subject: [PATCH 020/254] Using custom ceilf inline impl in ImGui::CalcTextSize(). Amend 7b0bf230, 4622fa4b6, 12b7977. (#791) --- imgui.cpp | 7 +++---- imgui.h | 2 +- imgui_internal.h | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 735deb9d4..28fc2aa3c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6199,10 +6199,9 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); // Round - // FIXME: This has been here since Dec 2015 (7b0bf230 then 4622fa4b6) but down the line we want this out. See #791. - // Investigate using ceilf or e.g. https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c, https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html - // The problem is that ceilf() has a measurable cost. Not high, but measurable! - text_size.x = IM_TRUNC(text_size.x + 0.999f); + // (see 7b0bf230, 4622fa4b6, #791 for details about this.) + // FIXME: Add a way to disable this. + text_size.x = ImCeilFast(text_size.x); return text_size; } diff --git a/imgui.h b/imgui.h index cc80fc80f..2aa8cb0d7 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.9 WIP" -#define IMGUI_VERSION_NUM 19281 +#define IMGUI_VERSION_NUM 19282 #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_internal.h b/imgui_internal.h index 70f2bad39..4b9e18dd1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -531,6 +531,7 @@ inline float ImFloor(float f) { return inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } inline float ImTrunc64(float f) { return (float)(ImS64)(f); } inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only. +inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the the bit-hack version (search for "0x1p120f"). inline int ImModPositive(int a, int b) { return (a + b) % b; } inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } From 1d33ea939f702e0fb1b4201f1032ee63993ba3e4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 May 2026 18:10:29 +0200 Subject: [PATCH 021/254] DrawList: added ImDrawListFlags_NoTextPixelSnap to disable snapping of AddText() coordinates for a given scope. (#3437, #9417, #2291) This may evolve into a per-call ImDrawFlags option as well. + clip_rect.Max.y early out may be applied before truncation. --- docs/CHANGELOG.txt | 10 ++++++---- imgui.h | 1 + imgui_draw.cpp | 20 +++++++++++++++----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1c5535835..932ebe129 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -50,15 +50,17 @@ Other Changes: - Clicking on a window's empty-space to move/focus a window checks for lack of queued focus request. (#9382) - InputText: - - Added style.InputTextCursorSize to configure cursor/caret thickness. (#7031, #9409) - This is automatically scaled by style.ScaleAllSizes(). + - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) + This is automatically scaled by `style.ScaleAllSizes()`. - Fonts: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) - - Tweak CalcTextSize() awkward width rounding/ceiling code to reduce floating-point + - Tweak `CalcTextSize()` awkward width rounding/ceiling code to reduce floating-point imprecisions altering the result by 1 even at relatively small width. (#791) - DrawList: - - Minor optimization to AddLine(), AddLineH(), AddLineV() functions. (#4091) + - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) + - Added `ImDrawListFlags_NoTextPixelSnap` to disable snapping of AddText() + coordinates for a given scope. (#3437, #9417, #2291) - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). diff --git a/imgui.h b/imgui.h index 2aa8cb0d7..050237048 100644 --- a/imgui.h +++ b/imgui.h @@ -3279,6 +3279,7 @@ enum ImDrawListFlags_ ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. + ImDrawListFlags_NoTextPixelSnap = 1 << 4, // [Internal] Disable automatically snapping AddText() calls to pixel boundaries. }; // Draw command list diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b112f471d..a78a35ac0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5764,8 +5764,13 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (glyph->Colored) col |= ~IM_COL32_A_MASK; float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; - float x = IM_TRUNC(pos.x); - float y = IM_TRUNC(pos.y); + float x = pos.x; + float y = pos.y; + if ((draw_list->Flags & ImDrawListFlags_NoTextPixelSnap) == 0) + { + x = IM_TRUNC(x); + y = IM_TRUNC(y); + } float x1 = x + glyph->X0 * scale; float x2 = x + glyph->X1 * scale; @@ -5797,12 +5802,17 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im // DO NOT CALL DIRECTLY THIS WILL CHANGE WILDLY IN 2026. Use ImDrawList::AddText(). void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, ImDrawTextFlags flags) { - // Align to be pixel perfect begin: - float x = IM_TRUNC(pos.x); - float y = IM_TRUNC(pos.y); + // Align to be pixel perfect + float x = pos.x; + float y = pos.y; if (y > clip_rect.w) return; + if ((draw_list->Flags & ImDrawListFlags_NoTextPixelSnap) == 0) + { + x = IM_TRUNC(x); + y = IM_TRUNC(y); + } if (!text_end) text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. From cac16b0d1691e6985dbfbd7e773b00259af45dd7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 29 May 2026 18:39:17 +0200 Subject: [PATCH 022/254] DrawList: rename ImDrawListFlags_NoTextPixelSnap -> ImDrawListFlags_TextNoPixelSnap. (#3437, #9417, #2291) Amend 1d33ea9 --- docs/CHANGELOG.txt | 2 +- imgui.h | 2 +- imgui_draw.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 932ebe129..55803f333 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -59,7 +59,7 @@ Other Changes: imprecisions altering the result by 1 even at relatively small width. (#791) - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - - Added `ImDrawListFlags_NoTextPixelSnap` to disable snapping of AddText() + - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() coordinates for a given scope. (#3437, #9417, #2291) - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' diff --git a/imgui.h b/imgui.h index 050237048..84c991727 100644 --- a/imgui.h +++ b/imgui.h @@ -3279,7 +3279,7 @@ enum ImDrawListFlags_ ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. - ImDrawListFlags_NoTextPixelSnap = 1 << 4, // [Internal] Disable automatically snapping AddText() calls to pixel boundaries. + ImDrawListFlags_TextNoPixelSnap = 1 << 4, // [Internal] Disable automatically snapping AddText() calls to pixel boundaries. }; // Draw command list diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a78a35ac0..090efb4d2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5766,7 +5766,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = pos.x; float y = pos.y; - if ((draw_list->Flags & ImDrawListFlags_NoTextPixelSnap) == 0) + if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); @@ -5808,7 +5808,7 @@ begin: float y = pos.y; if (y > clip_rect.w) return; - if ((draw_list->Flags & ImDrawListFlags_NoTextPixelSnap) == 0) + if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); From 70f02b05581b1b38a981ac375f25c81276b5c97a Mon Sep 17 00:00:00 2001 From: Dex <60656530+dexmoh@users.noreply.github.com> Date: Sun, 31 May 2026 13:54:30 +0200 Subject: [PATCH 023/254] Docs: Fix small grammar mistake in README. (#9423) --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 283dd479b..cc73e28ec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -43,7 +43,7 @@ Dear ImGui is particularly suited to integration in game engines (for tooling), **Backends for a variety of graphics API and rendering platforms** are provided in the [backends/](https://github.com/ocornut/imgui/tree/master/backends) folder, along with example applications in the [examples/](https://github.com/ocornut/imgui/tree/master/examples) folder. You may also create your own backend. Anywhere where you can render textured triangles, you can render Dear ImGui. -C++20 users wishing to use a module may the use [stripe2933/imgui-module](https://github.com/stripe2933/imgui-module) third-party extension. +C++20 users wishing to use a module may use the [stripe2933/imgui-module](https://github.com/stripe2933/imgui-module) third-party extension. See the [Getting Started & Integration](#getting-started--integration) section of this document for more details. From 3b99fc48832ab8d3930cca0d38a6327239df30a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 1 Jun 2026 14:23:17 +0200 Subject: [PATCH 024/254] ColorButton: small rendering tweak/optimization for the alpha checkerboard. Inverting the pattern means that for our typical 3x3 grid we don't need to draw 1+4 rounded rectangles, only 1. This also fix the slightly rounding mismatch when switching from a=255 to a<255 paths. --- docs/CHANGELOG.txt | 2 ++ imgui_draw.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 55803f333..76d855165 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -61,6 +61,8 @@ Other Changes: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() coordinates for a given scope. (#3437, #9417, #2291) +- ColorButton: + - Small rendering tweak/optimization for the alpha checkerboard. - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 090efb4d2..d8f9bf8e3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -6198,8 +6198,8 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p flags = ImDrawFlags_RoundCornersDefault_; if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { - ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); - ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); + ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); + ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); int yi = 0; @@ -6208,12 +6208,12 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); if (y2 <= y1) continue; - for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + for (float x = p_min.x + grid_off.x + ((yi ^ 1) & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) { float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); if (x2 <= x1) continue; - ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; + ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; // FIXME: Could use CalcRoundingFlagsForRectInRect() if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } From 3495e329f38b1e11df6607b26bdbadcdcee5b423 Mon Sep 17 00:00:00 2001 From: Flexan Date: Mon, 1 Jun 2026 15:34:00 +0200 Subject: [PATCH 025/254] Fixed small typo in demo code (#9425) --- imgui_demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 673a0248a..e0bbd9af6 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4121,7 +4121,7 @@ static void DemoWindowWidgetsTooltips() ImGui::BeginDisabled(); ImGui::Button("Disabled item", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) - ImGui::SetTooltip("I am a a tooltip for a disabled item."); + ImGui::SetTooltip("I am a tooltip for a disabled item."); ImGui::EndDisabled(); ImGui::TreePop(); From f241419b7856eda54908534dfea578d83bbbf0c2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 1 Jun 2026 16:48:48 +0200 Subject: [PATCH 026/254] Tabs: use AddRectFilled(). --- imgui_widgets.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index a98cb6936..6a6605279 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -10873,13 +10873,9 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI IM_UNUSED(flags); IM_ASSERT(width > 0.0f); const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); - const float y1 = bb.Min.y + 1.0f; + const float y1 = bb.Min.y + 1.0f; // Leave a bit of room in title bars. const float y2 = bb.Max.y - g.Style.TabBarBorderSize; - draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); - draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); - draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); - draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); - draw_list->PathFillConvex(col); + draw_list->AddRectFilled(bb.Min, ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop); if (g.Style.TabBorderSize > 0.0f) { draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); From 5aa0393a15f42f3d9274f6f9ea540b0b0e448ac5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 1 Jun 2026 17:23:11 +0200 Subject: [PATCH 027/254] DrawList: don't mark ImDrawListFlags_TextNoPixelSnap as internal in comments. (#3437, #9417, #2291) --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index 84c991727..d0c5ee8c6 100644 --- a/imgui.h +++ b/imgui.h @@ -3279,7 +3279,7 @@ enum ImDrawListFlags_ ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. - ImDrawListFlags_TextNoPixelSnap = 1 << 4, // [Internal] Disable automatically snapping AddText() calls to pixel boundaries. + ImDrawListFlags_TextNoPixelSnap = 1 << 4, // Disable automatically snapping AddText() calls to pixel boundaries. }; // Draw command list From 045a0907f4824ed3bfdb95bc8721f197f7d9734e Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 2 Jun 2026 18:05:23 +0200 Subject: [PATCH 028/254] Docs: Fonts.md: added notes about TexMinWidth,TexMinHeight. Probably misplaced: the document could have a section about memory usage considerations? --- docs/FONTS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/FONTS.md b/docs/FONTS.md index 1e51cde48..f0fbd85f9 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -77,6 +77,17 @@ Some solutions: You can use the `ImFontGlyphRangesBuilder` for this purpose and rebuilding your atlas between frames when new characters are needed. This will be the biggest win! - Set `io.Fonts.Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight;` to disable rounding the texture height to the next power of two. +### (5) Reduce texture resizes/copy on startup + +🆕 Since 1.92, the ImFontAtlas is initially created using a 512x128 texture, then grows as glyphs and fonts are registered. Growth leads to a copy, and both the old and new sized textures are present in memory. If you use known fonts and want to reduce initial growth, you may set `TexMinWidth` and `TexMinHeight` during initializaton. + +```cpp +ImFontAtlas* atlas = io.Fonts; +atlas->TexMinWidth = 1024; +atlas->TexMinHeight = 1024; +atlas->AddFont(...); +``` + ##### [Return to Index](#index) --------------------------------------- From a054a016e28d8fb13a5f2d48b7c51647aa28bc36 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 2 Jun 2026 18:09:48 +0200 Subject: [PATCH 029/254] Docs: Fonts: amends. --- docs/FONTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FONTS.md b/docs/FONTS.md index f0fbd85f9..ac93bca0c 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -79,7 +79,7 @@ Some solutions: ### (5) Reduce texture resizes/copy on startup -🆕 Since 1.92, the ImFontAtlas is initially created using a 512x128 texture, then grows as glyphs and fonts are registered. Growth leads to a copy, and both the old and new sized textures are present in memory. If you use known fonts and want to reduce initial growth, you may set `TexMinWidth` and `TexMinHeight` during initializaton. +🆕 Since 1.92, the ImFontAtlas is initially created using a 512x128 texture, then grows as glyphs and fonts are used. Atlas growth leads to alloc+copy, and both the old and new sized textures are present in memory for a short time (typically one frame). If you use known fonts and want to reduce initial growth, you may set `TexMinWidth` and `TexMinHeight` during initializaton. ```cpp ImFontAtlas* atlas = io.Fonts; From 995e4a65ff44892cc27cf28e5ee4e988337812e9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2026 18:37:01 +0200 Subject: [PATCH 030/254] (Breaking) TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 1 + imgui.h | 6 +++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 76d855165..9c3c8e68b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,9 @@ HOW TO UPDATE? Breaking Changes: +- TreeNode: commented out legacy name `ImGuiTreeNodeFlags_SpanTextWidth` which + was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. + Other Changes: - Windows: diff --git a/imgui.cpp b/imgui.cpp index 28fc2aa3c..c56e9eff1 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/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. - 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). - Before: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); - After: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); diff --git a/imgui.h b/imgui.h index d0c5ee8c6..9f7c3a162 100644 --- a/imgui.h +++ b/imgui.h @@ -1331,7 +1331,7 @@ enum ImGuiTreeNodeFlags_ #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiTreeNodeFlags_NavLeftJumpsBackHere = ImGuiTreeNodeFlags_NavLeftJumpsToParent, // Renamed in 1.92.0 - ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth, // Renamed in 1.90.7 + //ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth, // Renamed in 1.90.7 //ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; @@ -2614,7 +2614,7 @@ struct ImGuiIO //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92 (June 2025) + float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92.0 (June 2025) // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). @@ -3925,7 +3925,7 @@ struct ImFont IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, ImDrawTextFlags flags = 0); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } // Obsoleted old name in 1.92.0. Note how `scale` was to `size`. #endif // [Internal] Don't use! From 5a42cddcd2408074ece1f9231ceeb0ca221e6efe Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2026 18:40:14 +0200 Subject: [PATCH 031/254] Fixed a build issue when defined IMGUI_API + IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#9424) Amend 6df50a0 --- imgui.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 9f7c3a162..c4828e591 100644 --- a/imgui.h +++ b/imgui.h @@ -3431,8 +3431,8 @@ struct ImDrawList inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.0 inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.0 #else - IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding /*= 0.0f*/, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) = delete; + void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding /*= 0.0f*/, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; + void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness) = delete; inline void PathStroke(ImU32 col, ImDrawFlags flags /*= 0*/, float thickness /*= 1.0f*/) = delete; #endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) From c4eaac6d4856d4136b31f7db6e3974c6d846db4d Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2026 19:04:14 +0200 Subject: [PATCH 032/254] Fonts: fixed an issue where passing a manually created ImFontAtlas to CreateContext() would incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) # Conflicts: # docs/CHANGELOG.txt --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9c3c8e68b..4f8cf7054 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,8 @@ Other Changes: disable embedding either fonts separately. (#9407) - Tweak `CalcTextSize()` awkward width rounding/ceiling code to reduce floating-point imprecisions altering the result by 1 even at relatively small width. (#791) + - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would + incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imgui.cpp b/imgui.cpp index c56e9eff1..516bd9dec 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4476,7 +4476,7 @@ void ImGui::Shutdown() for (ImFontAtlas* atlas : g.FontAtlases) { UnregisterFontAtlas(atlas); - if (atlas->RefCount == 0) + if (atlas->RefCount == 0 && atlas->OwnerContext == &g) { atlas->Locked = false; IM_DELETE(atlas); From 14278db0240aefee9af8beb960053aaaea55dce3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Jun 2026 15:27:02 +0200 Subject: [PATCH 033/254] Fonts: better document the fact that ImFontAtlas::Clear()/ClearFonts() functions are unlikely to be useful nowadays + fix tex->Updates[] ever-growing if ClearFonts() is called between frames. --- docs/CHANGELOG.txt | 3 +++ imgui.h | 16 +++++++++------- imgui_draw.cpp | 43 ++++++++++++++++++++++++------------------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4f8cf7054..98bdb582d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,9 @@ Other Changes: disable embedding either fonts separately. (#9407) - Tweak `CalcTextSize()` awkward width rounding/ceiling code to reduce floating-point imprecisions altering the result by 1 even at relatively small width. (#791) + - Better document the fact that ImFontAtlas::Clear()/ClearFonts() functions are + unlikely to be useful nowadays. Better recover to an edge case of mistakenly + calling ClearFonts() during rendering. - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) - DrawList: diff --git a/imgui.h b/imgui.h index c4828e591..378a8827a 100644 --- a/imgui.h +++ b/imgui.h @@ -3545,7 +3545,7 @@ struct ImTextureData bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. // Functions - // - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear() instead of ClearFonts()? + // - If GetPixels() functions asserts while being called by your render loop, it could be caused by calling ImFontAtlas::Clear()/ClearFonts()? ImTextureData() { memset((void*)this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } ~ImTextureData() { DestroyPixels(); } IMGUI_API void Create(ImTextureFormat format, int w, int h); @@ -3699,14 +3699,16 @@ struct ImFontAtlas IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. - IMGUI_API void RemoveFont(ImFont* font); - - IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! - IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. You can call this mid-frame if you load new fonts afterwards! - IMGUI_API void CompactCache(); // Compact cached glyphs and texture. + IMGUI_API void RemoveFont(ImFont* font); // Remove a font + IMGUI_API void CompactCache(); // Compact cached glyphs and texture. IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. - // As we are transitioning toward a new font system, we expect to obsolete those soon: + // Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts. + // - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards. + // - As we are transitioning toward our new font system the semantic for those functions gets increasingly misleading and are often a source of issues. + // TL;DR; most likely, don't use any of those functions. We expect to obsolete/rework them. + IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! + IMGUI_API void ClearFonts(); // Clear input+output font data/glyphs. New fonts and textures will be recreated afterwards. IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d8f9bf8e3..03376e02d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2691,6 +2691,7 @@ ImFontAtlas::~ImFontAtlas() // Calling this mid-frame will discard the CPU-side copy of the texture data which is generally unreliable as you may have textures queued for creation or updates. void ImFontAtlas::Clear() { + IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlas::Clear()\n"); bool backup_renderer_has_textures = RendererHasTextures; RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. ClearFonts(); @@ -2701,6 +2702,7 @@ void ImFontAtlas::Clear() void ImFontAtlas::ClearFonts() { // FIXME-NEWATLAS: Illegal to remove currently bound font. + IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlas::ClearFonts()\n"); IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFont* font : Fonts) ImFontAtlasBuildNotifySetFont(this, font, NULL); @@ -2776,6 +2778,28 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice. atlas->RendererHasTextures = renderer_has_textures; + // Update texture status and discard old textures. + // (we do this first thing to handle an edge case: if user mistakenly calls ClearFonts()+SetStatus(OK) during + // rendering, it would ImFontAtlasBuildMain() rebuilding before tex->Updates[] gets a chance to be cleared) + // (if somehow we need to move this back lower in the function, we could manually call the code to clear Updates[]). + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + // Update and remove if requested + ImTextureData* tex = atlas->TexList[tex_n]; + if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); + + bool remove_from_list = ImTextureDataUpdateNewFrame(tex); + if (remove_from_list) + { + IM_ASSERT(atlas->TexData != tex); + tex->DestroyPixels(); + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; + } + } + // Check that font atlas was built or backend support texture reload in which case we can build now if (atlas->RendererHasTextures) { @@ -2815,25 +2839,6 @@ void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool rendere builder->BakedPool.Size -= builder->BakedDiscardedCount; builder->BakedDiscardedCount = 0; } - - // Update texture status - for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) - { - // Update and remove if requested - ImTextureData* tex = atlas->TexList[tex_n]; - if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) - IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); - - bool remove_from_list = ImTextureDataUpdateNewFrame(tex); - if (remove_from_list) - { - IM_ASSERT(atlas->TexData != tex); - tex->DestroyPixels(); - IM_DELETE(tex); - atlas->TexList.erase(atlas->TexList.begin() + tex_n); - tex_n--; - } - } } bool ImTextureDataUpdateNewFrame(ImTextureData* tex) From 7950c96f0e86b761607a34601f19e90afa825bd6 Mon Sep 17 00:00:00 2001 From: Vladimir Perminov Date: Wed, 3 Jun 2026 04:08:21 +0300 Subject: [PATCH 034/254] Backends: OpenGL3: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) Amend fc737d23339 --- backends/imgui_impl_opengl3.cpp | 6 +++++- docs/CHANGELOG.txt | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 261ab00e0..a9e32b7ac 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-06-23: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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) @@ -1048,7 +1049,10 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) #elif defined(__APPLE__) glsl_version = "#version 150"; #else - glsl_version = "#version 130"; + if (bd->GlVersion >= 410) + glsl_version = "#version 410"; + else + glsl_version = "#version 130"; #endif } IM_ASSERT((int)strlen(glsl_version) + 2 < IM_COUNTOF(bd->GlslVersionString)); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 98bdb582d..192a30523 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -75,6 +75,9 @@ Other Changes: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). - Backends: + - OpenGL3: + - GLSL version detection assume GLSL 410 when GL context is 4.1. + Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Win32: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends From fc5e2f28fedf6bbe0d20885a5a144d1206a3474e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Jun 2026 19:24:06 +0200 Subject: [PATCH 035/254] Examples: WGPU: fixed typo for wvgk cloning. (#9428, #9387) --- examples/example_glfw_wgpu/CMakeLists.txt | 2 +- examples/example_sdl2_wgpu/CMakeLists.txt | 2 +- examples/example_sdl3_wgpu/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example_glfw_wgpu/CMakeLists.txt b/examples/example_glfw_wgpu/CMakeLists.txt index 95d9fe38a..287a89d62 100644 --- a/examples/example_glfw_wgpu/CMakeLists.txt +++ b/examples/example_glfw_wgpu/CMakeLists.txt @@ -14,7 +14,7 @@ # * build/example_glfw_wgpu[.exe] or build/Debug/example_glfw_wgpu[.exe] # Building for desktop with WGVK (MUCH EASIER) -# 1. git clone https://github.com/manuel5975p/WGVK dawn +# 1. git clone https://github.com/manuel5975p/WGVK wgvk # 2. cmake -B build -DIMGUI_WGVK_DIR=wgvk # 3. cmake --build build # The resulting binary will be found at one of the following locations: diff --git a/examples/example_sdl2_wgpu/CMakeLists.txt b/examples/example_sdl2_wgpu/CMakeLists.txt index aa8a1e575..efe35ff61 100644 --- a/examples/example_sdl2_wgpu/CMakeLists.txt +++ b/examples/example_sdl2_wgpu/CMakeLists.txt @@ -14,7 +14,7 @@ # * build/example_sdl2_wgpu[.exe] or build/Debug/example_sdl2_wgpu[.exe] # Building for desktop with WGVK (MUCH EASIER) -# 1. git clone https://github.com/manuel5975p/WGVK dawn +# 1. git clone https://github.com/manuel5975p/WGVK wgvk # 2. cmake -B build -DIMGUI_WGVK_DIR=wgvk # 3. cmake --build build # The resulting binary will be found at one of the following locations: diff --git a/examples/example_sdl3_wgpu/CMakeLists.txt b/examples/example_sdl3_wgpu/CMakeLists.txt index 7ecf0266d..dd7b1e40b 100644 --- a/examples/example_sdl3_wgpu/CMakeLists.txt +++ b/examples/example_sdl3_wgpu/CMakeLists.txt @@ -14,7 +14,7 @@ # * build/example_sdl3_wgpu[.exe] or build/Debug/example_sdl3_wgpu[.exe] # Building for desktop with WGVK (MUCH EASIER) -# 1. git clone https://github.com/manuel5975p/WGVK dawn +# 1. git clone https://github.com/manuel5975p/WGVK wgvk # 2. cmake -B build -DIMGUI_WGVK_DIR=wgvk # 3. cmake --build build # The resulting binary will be found at one of the following locations: From 6acba3b47d2ac4c7bb5ffb6ab04bcd896b3d3658 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Jun 2026 20:57:51 +0200 Subject: [PATCH 036/254] ImDrawListSharedData: rename CircleSegmentMaxError to CircleTessellationMaxError. Remove misleading ArcFastRadiusCutoff assignment. cc 051ce0765, f107693d9 (3808) --- imgui_draw.cpp | 11 +++++------ imgui_internal.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 03376e02d..4a68ff4a8 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -402,7 +402,6 @@ ImDrawListSharedData::ImDrawListSharedData() const float a = ((float)i * 2 * IM_PI) / (float)IM_COUNTOF(ArcFastVtx); ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } - ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } ImDrawListSharedData::~ImDrawListSharedData() @@ -412,17 +411,17 @@ ImDrawListSharedData::~ImDrawListSharedData() void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) { - if (CircleSegmentMaxError == max_error) + if (CircleTessellationMaxError == max_error) return; IM_ASSERT(max_error > 0.0f); - CircleSegmentMaxError = max_error; + CircleTessellationMaxError = max_error; for (int i = 0; i < IM_COUNTOF(CircleSegmentCounts); i++) { const float radius = (float)i; - CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX); + CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleTessellationMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX); } - ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleTessellationMaxError); } ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) @@ -660,7 +659,7 @@ int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const if (radius_idx >= 0 && radius_idx < IM_COUNTOF(_Data->CircleSegmentCounts)) return _Data->CircleSegmentCounts[radius_idx]; // Use cached value else - return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); + return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleTessellationMaxError); } // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) diff --git a/imgui_internal.h b/imgui_internal.h index 4b9e18dd1..3444a20b5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -873,7 +873,7 @@ struct IMGUI_API ImDrawListSharedData float FontSize; // Current font size (used for for simplified AddText overload) float FontScale; // Current font scale (== FontSize / Font->FontSize) float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() - float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc + float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc float InitialFringeScale; // Initial scale to apply to AA fringe ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() From 5220a3f48d39c000da8b46fb0fccf3dd62695c51 Mon Sep 17 00:00:00 2001 From: Oliver Dixon Date: Sun, 7 Jun 2026 08:58:58 +0100 Subject: [PATCH 037/254] Examples: WebGPU: Require C++20 to build Dawn examples (#9433, #9432) --- examples/example_glfw_wgpu/CMakeLists.txt | 2 +- examples/example_sdl2_wgpu/CMakeLists.txt | 2 +- examples/example_sdl3_wgpu/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example_glfw_wgpu/CMakeLists.txt b/examples/example_glfw_wgpu/CMakeLists.txt index 287a89d62..4eaaee762 100644 --- a/examples/example_glfw_wgpu/CMakeLists.txt +++ b/examples/example_glfw_wgpu/CMakeLists.txt @@ -37,7 +37,7 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() -set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 +set(CMAKE_CXX_STANDARD 20) # Dawn requires C++20 # Dear ImGui set(IMGUI_DIR ../../) diff --git a/examples/example_sdl2_wgpu/CMakeLists.txt b/examples/example_sdl2_wgpu/CMakeLists.txt index efe35ff61..8795070c3 100644 --- a/examples/example_sdl2_wgpu/CMakeLists.txt +++ b/examples/example_sdl2_wgpu/CMakeLists.txt @@ -37,7 +37,7 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() -set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 +set(CMAKE_CXX_STANDARD 10) # Dawn requires C++20 # Dear ImGui set(IMGUI_DIR ../../) diff --git a/examples/example_sdl3_wgpu/CMakeLists.txt b/examples/example_sdl3_wgpu/CMakeLists.txt index dd7b1e40b..21a72bd0e 100644 --- a/examples/example_sdl3_wgpu/CMakeLists.txt +++ b/examples/example_sdl3_wgpu/CMakeLists.txt @@ -37,7 +37,7 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() -set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 +set(CMAKE_CXX_STANDARD 20) # Dawn requires C++20 # Dear ImGui set(IMGUI_DIR ../../) From f1dd18b75c4f08941ba691a75dad65b884415cb3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 10 Jun 2026 15:55:48 +0200 Subject: [PATCH 038/254] Docs: minor amends to ImTextureData comments. --- imgui.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 378a8827a..cd6cb2446 100644 --- a/imgui.h +++ b/imgui.h @@ -3513,7 +3513,7 @@ enum ImTextureStatus // You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. struct ImTextureRect { - unsigned short x, y; // Upper-left coordinates of rectangle to update + unsigned short x, y; // Upper-left coordinates of rectangle to update, within the parent Pixels[] array. unsigned short w, h; // Size of rectangle to update (in pixels) }; @@ -3535,7 +3535,7 @@ struct ImTextureData int Width; // w r // Texture width int Height; // w r // Texture height int BytesPerPixel; // w r // 4 or 1 - unsigned char* Pixels; // w r // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + unsigned char* Pixels; // w r // Pointer to whole texture buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. ImTextureRect UsedRect; // w r // Bounding box encompassing all past and queued Updates[]. ImTextureRect UpdateRect; // w r // Bounding box encompassing all queued Updates[]. ImVector Updates; // w r // Array of individual updates. From 4312c4399f369424c4517f91836eb4356e19019d Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 9 Jun 2026 17:16:21 +0200 Subject: [PATCH 039/254] Tables: moved RefScale update and application to TableUpdateLayout(). --- imgui_tables.cpp | 51 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 98dd7baad..f510d3f93 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -615,21 +615,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsSettingsRequestLoad) TableLoadSettings(table); - // Handle DPI/font resize - // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. - // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. - // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. - // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. - const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? - if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) - { - const float scale_factor = new_ref_scale_unit / table->RefScale; - //IMGUI_DEBUG_PRINT("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); - for (int n = 0; n < columns_count; n++) - table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; - } - table->RefScale = new_ref_scale_unit; - // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. @@ -851,11 +836,27 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) ImGuiContext& g = *GImGui; IM_ASSERT(table->IsLayoutLocked == false); + // Handle DPI/font resize + // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. + // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. + // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. + // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. + const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? + const int columns_count = table->ColumnsCount; + if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) + { + const float scale_factor = new_ref_scale_unit / table->RefScale; + //IMGUI_DEBUG_PRINT("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); + for (int n = 0; n < columns_count; n++) + table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; + } + table->RefScale = new_ref_scale_unit; + const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); table->IsDefaultDisplayOrder = true; table->ColumnsEnabledCount = 0; - ImBitArrayClearAllBits(table->EnabledMaskByIndex, table->ColumnsCount); - ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, table->ColumnsCount); + ImBitArrayClearAllBits(table->EnabledMaskByIndex, columns_count); + ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, columns_count); table->LeftMostEnabledColumn = -1; table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE @@ -868,7 +869,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) bool has_resizable = false; float stretch_sum_width_auto = 0.0f; float fixed_max_width_auto = 0.0f; - for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + for (int order_n = 0; order_n < columns_count; order_n++) { const int column_n = table->DisplayOrderToIndex[order_n]; if (column_n != order_n) @@ -967,7 +968,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) float sum_width_requests = 0.0f; // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns but including spacing/padding. float stretch_sum_weights = 0.0f; // Sum of all weights for stretch columns. table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + for (int column_n = 0; column_n < columns_count; column_n++) { if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) continue; @@ -1031,7 +1032,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests; float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + for (int column_n = 0; column_n < columns_count; column_n++) { if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) continue; @@ -1058,7 +1059,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). // Using right-to-left distribution (more likely to match resizing cursor). if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths)) - for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) + for (int order_n = columns_count - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) { if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) continue; @@ -1098,8 +1099,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; ImRect host_clip_rect = table->InnerClipRect; //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2; - ImBitArrayClearAllBits(table->VisibleMaskByIndex, table->ColumnsCount); - for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + ImBitArrayClearAllBits(table->VisibleMaskByIndex, columns_count); + for (int order_n = 0; order_n < columns_count; order_n++) { const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -1247,7 +1248,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const float unused_x1 = ImMax(table->WorkRect.Min.x, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x); if (is_hovering_table && table->HoveredColumnBody == -1) if (mouse_skewed_x >= unused_x1) - table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount; + table->HoveredColumnBody = (ImGuiTableColumnIdx)columns_count; if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable)) table->Flags &= ~ImGuiTableFlags_Resizable; @@ -1289,7 +1290,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->HighlightColumnHeader = -1; if (table->IsContextPopupOpen && table->ContextPopupColumn != -1 && table->InstanceInteracted == table->InstanceCurrent) table->HighlightColumnHeader = table->ContextPopupColumn; - else if ((table->Flags & ImGuiTableFlags_HighlightHoveredColumn) && table->HoveredColumnBody != -1 && table->HoveredColumnBody != table->ColumnsCount && table->HoveredColumnBorder == -1) + else if ((table->Flags & ImGuiTableFlags_HighlightHoveredColumn) && table->HoveredColumnBody != -1 && table->HoveredColumnBody != columns_count && table->HoveredColumnBorder == -1) if (g.ActiveId == 0 || (table->IsActiveIdInTable || g.DragDropActive)) table->HighlightColumnHeader = table->HoveredColumnBody; From aa377352ef32e096ea439eafffcaed063f678061 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 8 Jun 2026 16:39:10 +0200 Subject: [PATCH 040/254] Tables: TableGcCompactTransientBuffers() can ditch AngledHeadersRequests data. Amend b30df8890.(#6917) --- imgui_internal.h | 2 +- imgui_tables.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui_internal.h b/imgui_internal.h index 3444a20b5..4bf7e40ab 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3112,7 +3112,7 @@ struct IMGUI_API ImGuiTableTempData int TableIndex; // Index in g.Tables.Buf[] pool float LastTimeActive; // Last timestamp this structure was used float AngledHeadersExtraWidth; // Used in EndTable() - ImVector AngledHeadersRequests; // Used in TableAngledHeadersRow() + ImVector AngledHeadersRequests; // Used in TableAngledHeadersRow() // FIXME: Single instance is enough? ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() ImDrawListSplitter DrawSplitter; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f510d3f93..3e3247806 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4076,6 +4076,7 @@ void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data) { + temp_data->AngledHeadersRequests.clear(); temp_data->DrawSplitter.ClearFreeMemory(); temp_data->LastTimeActive = -1.0f; } From b10137f92f594e217716ff9888ef5f4e2721167a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 10 Jun 2026 17:18:25 +0200 Subject: [PATCH 041/254] Internals: move function declarations into Tables section. --- imgui_internal.h | 155 ++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 4bf7e40ab..a843e4767 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -35,7 +35,7 @@ Index of this file: // [SECTION] ImGuiContext (main imgui context) // [SECTION] ImGuiWindowTempData, ImGuiWindow // [SECTION] Tab bar, Tab item support -// [SECTION] Table support +// [SECTION] Table support + internal API // [SECTION] ImGui internal API // [SECTION] ImFontLoader // [SECTION] ImFontAtlas internal API @@ -2878,7 +2878,7 @@ struct IMGUI_API ImGuiTabBar }; //----------------------------------------------------------------------------- -// [SECTION] Table support +// [SECTION] Table support + internal API //----------------------------------------------------------------------------- #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. @@ -3167,6 +3167,83 @@ struct ImGuiTableSettings ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } }; +namespace ImGui +{ + // Tables: Candidates for public API + IMGUI_API void TableOpenContextMenu(int column_n = -1); + IMGUI_API void TableSetColumnWidth(int column_n, float width); + IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); + IMGUI_API int TableGetHoveredRow(); // Retrieve *PREVIOUS FRAME* hovered row. This difference with TableGetHoveredColumn() is the reason why this is not public yet. + IMGUI_API float TableGetHeaderRowHeight(); + IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); + IMGUI_API void TablePushBackgroundChannel(); + IMGUI_API void TablePopBackgroundChannel(); + IMGUI_API void TablePushColumnChannel(int column_n); + IMGUI_API void TablePopColumnChannel(); + IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); + + // Tables: Internals + inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } + IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); + IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); + IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count); + IMGUI_API void TableBeginApplyRequests(ImGuiTable* table); + IMGUI_API void TableSetupDrawChannels(ImGuiTable* table); + IMGUI_API void TableUpdateLayout(ImGuiTable* table); + IMGUI_API void TableUpdateBorders(ImGuiTable* table); + IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); + IMGUI_API void TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect); + IMGUI_API void TableDrawBorders(ImGuiTable* table); + IMGUI_API void TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display); + IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable* table); + IMGUI_API void TableMergeDrawChannels(ImGuiTable* table); + inline ImGuiTableInstanceData* TableGetInstanceData(ImGuiTable* table, int instance_no) { if (instance_no == 0) return &table->InstanceDataFirst; return &table->InstanceDataExtra[instance_no - 1]; } + inline ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no) { return TableGetInstanceData(table, instance_no)->TableInstanceID; } + IMGUI_API void TableFixDisplayOrder(ImGuiTable* table); + IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); + IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); + IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column); + IMGUI_API void TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column); + IMGUI_API float TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column); + IMGUI_API void TableBeginRow(ImGuiTable* table); + IMGUI_API void TableEndRow(ImGuiTable* table); + IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); + IMGUI_API void TableEndCell(ImGuiTable* table); + IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); + IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); + IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); + IMGUI_API float TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); + IMGUI_API void TableSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order); + IMGUI_API void TableQueueSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order); + IMGUI_API void TableRemove(ImGuiTable* table); + IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); + IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table); + IMGUI_API void TableGcCompactSettings(); + + // Tables: Settings + IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableSaveSettings(ImGuiTable* table); + IMGUI_API void TableResetSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); + IMGUI_API void TableSettingsAddSettingsHandler(); + IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); + IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); + + // Legacy Columns API (this is not exposed because we will encourage transitioning to the Tables API) + IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); + IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). + IMGUI_API void EndColumns(); // close columns + IMGUI_API void PushColumnClipRect(int column_index); + IMGUI_API void PushColumnsBackground(); + IMGUI_API void PopColumnsBackground(); + IMGUI_API ImGuiID GetColumnsID(const char* str_id, int count); + IMGUI_API ImGuiOldColumns* FindOrCreateColumns(ImGuiWindow* window, ImGuiID id); + IMGUI_API float GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm); + IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); +} + //----------------------------------------------------------------------------- // [SECTION] ImGui internal API // No guarantee of forward compatibility here! @@ -3520,80 +3597,6 @@ namespace ImGui inline ImGuiBoxSelectState* GetBoxSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; } inline ImGuiMultiSelectState* GetMultiSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return g.MultiSelectStorage.GetByKey(id); } - // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) - IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); - IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). - IMGUI_API void EndColumns(); // close columns - IMGUI_API void PushColumnClipRect(int column_index); - IMGUI_API void PushColumnsBackground(); - IMGUI_API void PopColumnsBackground(); - IMGUI_API ImGuiID GetColumnsID(const char* str_id, int count); - IMGUI_API ImGuiOldColumns* FindOrCreateColumns(ImGuiWindow* window, ImGuiID id); - IMGUI_API float GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm); - IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); - - // Tables: Candidates for public API - IMGUI_API void TableOpenContextMenu(int column_n = -1); - IMGUI_API void TableSetColumnWidth(int column_n, float width); - IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); - IMGUI_API int TableGetHoveredRow(); // Retrieve *PREVIOUS FRAME* hovered row. This difference with TableGetHoveredColumn() is the reason why this is not public yet. - IMGUI_API float TableGetHeaderRowHeight(); - IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); - IMGUI_API void TablePushBackgroundChannel(); - IMGUI_API void TablePopBackgroundChannel(); - IMGUI_API void TablePushColumnChannel(int column_n); - IMGUI_API void TablePopColumnChannel(); - IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); - - // Tables: Internals - inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } - IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); - IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); - IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count); - IMGUI_API void TableBeginApplyRequests(ImGuiTable* table); - IMGUI_API void TableSetupDrawChannels(ImGuiTable* table); - IMGUI_API void TableUpdateLayout(ImGuiTable* table); - IMGUI_API void TableUpdateBorders(ImGuiTable* table); - IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); - IMGUI_API void TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect); - IMGUI_API void TableDrawBorders(ImGuiTable* table); - IMGUI_API void TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display); - IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable* table); - IMGUI_API void TableMergeDrawChannels(ImGuiTable* table); - inline ImGuiTableInstanceData* TableGetInstanceData(ImGuiTable* table, int instance_no) { if (instance_no == 0) return &table->InstanceDataFirst; return &table->InstanceDataExtra[instance_no - 1]; } - inline ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no) { return TableGetInstanceData(table, instance_no)->TableInstanceID; } - IMGUI_API void TableFixDisplayOrder(ImGuiTable* table); - IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); - IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); - IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column); - IMGUI_API void TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column); - IMGUI_API float TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column); - IMGUI_API void TableBeginRow(ImGuiTable* table); - IMGUI_API void TableEndRow(ImGuiTable* table); - IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); - IMGUI_API void TableEndCell(ImGuiTable* table); - IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable* table, int column_n); - IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); - IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); - IMGUI_API float TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n); - IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n); - IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable* table); - IMGUI_API void TableSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order); - IMGUI_API void TableQueueSetColumnDisplayOrder(ImGuiTable* table, int column_n, int dst_order); - IMGUI_API void TableRemove(ImGuiTable* table); - IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); - IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table); - IMGUI_API void TableGcCompactSettings(); - - // Tables: Settings - IMGUI_API void TableLoadSettings(ImGuiTable* table); - IMGUI_API void TableSaveSettings(ImGuiTable* table); - IMGUI_API void TableResetSettings(ImGuiTable* table); - IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); - IMGUI_API void TableSettingsAddSettingsHandler(); - IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); - IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); - // Tab Bars inline ImGuiTabBar* GetCurrentTabBar() { ImGuiContext& g = *GImGui; return g.CurrentTabBar; } IMGUI_API ImGuiTabBar* TabBarFindByID(ImGuiID id); From e5ff2d07d732e6145f5183d5e8116e13c386ab4d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 11 Jun 2026 18:39:53 +0200 Subject: [PATCH 042/254] Tables: Context-menu: added "Reset" sub-menu and "Reset Visibility" option. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 3 +++ imgui.h | 2 +- imgui_internal.h | 7 ++++++- imgui_tables.cpp | 43 ++++++++++++++++++++++++++++--------------- imgui_widgets.cpp | 2 +- 6 files changed, 42 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 192a30523..4f600ed78 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,9 @@ Other Changes: - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. +- Tables: + - Context Menu: added a "Reset" sub-menu with a "Reset Visibility" option. + (which is greyed out when using default settings) - Fonts: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) diff --git a/imgui.cpp b/imgui.cpp index 516bd9dec..1e2817136 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4161,7 +4161,10 @@ static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { ImGuiLocKey_TableSizeOne, "Size column to fit###SizeOne" }, { ImGuiLocKey_TableSizeAllFit, "Size all columns to fit###SizeAll" }, { ImGuiLocKey_TableSizeAllDefault, "Size all columns to default###SizeAll" }, + { ImGuiLocKey_TableReset, "Reset" }, + //{ ImGuiLocKey_TableResetAll, "Reset to default###ResetAll" }, { ImGuiLocKey_TableResetOrder, "Reset order###ResetOrder" }, + { ImGuiLocKey_TableResetVisibility, "Reset visibility###ResetVisibility" }, { ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" }, { ImGuiLocKey_WindowingPopup, "(Popup)" }, { ImGuiLocKey_WindowingUntitled, "(Untitled)" }, diff --git a/imgui.h b/imgui.h index cd6cb2446..88419d6f8 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.9 WIP" -#define IMGUI_VERSION_NUM 19282 +#define IMGUI_VERSION_NUM 19283 #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_internal.h b/imgui_internal.h index a843e4767..0c95dd23e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2047,7 +2047,10 @@ enum ImGuiLocKey : int ImGuiLocKey_TableSizeOne, ImGuiLocKey_TableSizeAllFit, ImGuiLocKey_TableSizeAllDefault, + ImGuiLocKey_TableReset, + //ImGuiLocKey_TableResetAll, ImGuiLocKey_TableResetOrder, + ImGuiLocKey_TableResetVisibility, ImGuiLocKey_WindowingMainMenuBar, ImGuiLocKey_WindowingPopup, ImGuiLocKey_WindowingUntitled, @@ -3086,8 +3089,10 @@ struct IMGUI_API ImGuiTable bool IsSettingsRequestLoad; bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSettings data. bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) - bool IsResetAllRequest; + bool IsDefaultVisibility; // Set when enabled/visibility is unchanged from default + bool IsResetAllRequest; // Set to queue a call to TableResetSettings() in BeginTable() bool IsResetDisplayOrderRequest; + bool IsResetVisibilityRequest; bool IsUnfrozenRows; // Set when we got past the frozen row. bool IsDefaultSizingPolicy; // Set if user didn't explicitly set a sizing policy in BeginTable() bool IsActiveIdAliveBeforeTable; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3e3247806..a306a0077 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -704,7 +704,7 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) // table->ReorderColumn = -1; } - // Handle display order reset request + // Handle display order / visibility reset requests if (table->IsResetDisplayOrderRequest) { for (int n = 0; n < table->ColumnsCount; n++) @@ -712,6 +712,13 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) table->IsResetDisplayOrderRequest = false; table->IsSettingsDirty = true; } + if (table->IsResetVisibilityRequest) + { + for (ImGuiTableColumn& column : table->Columns) + column.IsUserEnabled = column.IsUserEnabledNextFrame = (column.Flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1; + table->IsResetVisibilityRequest = false; + table->IsSettingsDirty = true; + } } // Apply immediately. See TableQueueSetColumnDisplayOrder() for additional checks/constraints. @@ -853,7 +860,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->RefScale = new_ref_scale_unit; const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); - table->IsDefaultDisplayOrder = true; + table->IsDefaultDisplayOrder = table->IsDefaultVisibility = true; table->ColumnsEnabledCount = 0; ImBitArrayClearAllBits(table->EnabledMaskByIndex, columns_count); ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, columns_count); @@ -872,8 +879,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) for (int order_n = 0; order_n < columns_count; order_n++) { const int column_n = table->DisplayOrderToIndex[order_n]; - if (column_n != order_n) - table->IsDefaultDisplayOrder = false; ImGuiTableColumn* column = &table->Columns[column_n]; // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame. @@ -897,6 +902,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0; + if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1)) + table->IsDefaultVisibility = false; + if (column_n != order_n) + table->IsDefaultDisplayOrder = false; + if (column->SortOrder != -1 && !column->IsEnabled) table->IsSortSpecsDirty = true; if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti)) @@ -3598,17 +3608,20 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags want_separator = true; } - // Ordering - if (flags_for_section_to_display & ImGuiTableFlags_Reorderable) - { - if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder)) - table->IsResetDisplayOrderRequest = true; - want_separator = true; - } - - // Reset all (should work but seems unnecessary/noisy to expose?) - //if (MenuItem("Reset all")) - // table->IsResetAllRequest = true; + // Reset Order/Visibility etc. + if (flags_for_section_to_display & (ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + if (BeginMenu(LocalizeGetMsg(ImGuiLocKey_TableReset))) + { + //if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetAll))) // FIXME: Hiding because altering Sort Order + requesting auto-fit simultaneously has a tendency to exhibit a sizing glitch. + // table->IsResetAllRequest = true; + if (flags_for_section_to_display & ImGuiTableFlags_Reorderable) + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder)) // PS: cannot be hidden because it would mess with drag reordering. + table->IsResetDisplayOrderRequest = true; + if (flags_for_section_to_display & ImGuiTableFlags_Hideable) + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetVisibility), NULL, false, !table->IsDefaultVisibility)) + table->IsResetVisibilityRequest = true; + EndMenu(); + } // Sorting // (modify TableOpenContextMenu() to add _Sortable flag if enabling this) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6a6605279..4ea116234 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9638,7 +9638,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.) float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f; - float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); + float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); // FIXME: Always accounted for, even in a menu with none, because 'selected' has no neutral setting. float min_w = offsets->DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); ImVec2 text_pos(pos.x, pos.y + window->DC.CurrLineTextBaseOffset); From 924f57181b4f7cdde1f11dc25d29e8bb497a90ed Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 12 Jun 2026 18:22:45 +0200 Subject: [PATCH 043/254] Tables: Headers: fixed label being clipped early to reserve space for a sort marker even when no sort marker is displayed. Amend 892b48e2d6: while fixing auto fit this was ditched. --- docs/CHANGELOG.txt | 3 +++ imgui_tables.cpp | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4f600ed78..eb1e84202 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -58,6 +58,9 @@ Other Changes: - Tables: - Context Menu: added a "Reset" sub-menu with a "Reset Visibility" option. (which is greyed out when using default settings) + - Headers: fixed label being clipped early to reserve space for a sort marker + even when no sort marker is displayed. Auto-fitting a column still accounts + for the possible marker, so that sorting after an auto-fit doesn't clip the label. - Fonts: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a306a0077..405f6852f 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3288,7 +3288,9 @@ void ImGui::TableHeader(const char* label) } // Sort order arrow - const float ellipsis_max = ImMax(cell_r.Max.x - w_arrow - w_sort_text, label_pos.x); + // - Only clip label for a visible arrow. Auto-fit still accounts for a possible arrow, but when + // manually sized smaller we don't clip the label for the sake of an arrow that isn't displayed. + const float ellipsis_max = ImMax(cell_r.Max.x - (sort_arrow ? w_arrow + w_sort_text : 0.0f), label_pos.x); if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) { if (column->SortOrder != -1) From 2e0f9490398dd2d78ff4607cde500d581a725516 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 11 Jun 2026 14:30:02 +0200 Subject: [PATCH 044/254] Tables, Debug Log: added IMGUI_DEBUG_LOG_TABLE, ImGuiDebugLogFlags_EventTable. Clicking "Clear settings" calls TableResetSettings() immediately. --- imgui.cpp | 1 + imgui_internal.h | 5 ++++- imgui_tables.cpp | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 1e2817136..bbb29f550 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -17909,6 +17909,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); + ShowDebugLogFlag("Table", ImGuiDebugLogFlags_EventTable); ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting); if (SmallButton("Clear")) diff --git a/imgui_internal.h b/imgui_internal.h index 0c95dd23e..4b5e5be94 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -247,6 +247,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_POPUP(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_NAV(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventNav) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_TABLE(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventTable) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context. @@ -2104,8 +2105,9 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventInputRouting = 1 << 9, ImGuiDebugLogFlags_EventDocking = 1 << 10, // Unused in this branch ImGuiDebugLogFlags_EventViewport = 1 << 11, // Unused in this branch + ImGuiDebugLogFlags_EventTable = 1 << 12, - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventTable | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToDebugger = 1 << 21, // Also send output to Debugger Console [Windows only] ImGuiDebugLogFlags_OutputToTestEngine = 1 << 22, // Also send output to Dear ImGui Test Engine @@ -3229,6 +3231,7 @@ namespace ImGui // Tables: Settings IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableLoadSettingsForColumns(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table); IMGUI_API void TableResetSettings(ImGuiTable* table); IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 405f6852f..2174d2164 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -43,6 +43,7 @@ Index of this file: // | TableBeginApplyRequests() - apply queued resizing/reordering/hiding requests // | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) // | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width +//----------------------------------------------------------------------------- // - TableSetupColumn() user submit columns details (optional) // - TableSetupScrollFreeze() user submit scroll freeze information (optional) //----------------------------------------------------------------------------- @@ -567,6 +568,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (old_columns_count != 0 && old_columns_count != columns_count) { // Attempt to preserve width and other settings on column count/specs change (#4046) + IMGUI_DEBUG_LOG_TABLE("[table] Table 0x%08X column count %d -> %d, recreating storage.\n", table->ID, old_columns_count, columns_count); old_columns_to_preserve = table->Columns.Data; old_columns_raw_data = table->RawData; // Free at end of function table->RawData = NULL; @@ -853,7 +855,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) { const float scale_factor = new_ref_scale_unit / table->RefScale; - //IMGUI_DEBUG_PRINT("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); + IMGUI_DEBUG_LOG_TABLE("[table] 0x%08X RefScale %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScale, new_ref_scale_unit, scale_factor); for (int n = 0; n < columns_count; n++) table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; } @@ -4203,7 +4205,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) DebugNodeTableSettings(settings); if (clear_settings) - table->IsResetAllRequest = true; + table->IsResetAllRequest = true; // Queue a call to TableResetSettings() TreePop(); } From 36ea9728d1d4e22e9fa0b1eb9ba3694d104c5ac2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 11 Dec 2025 18:19:39 +0100 Subject: [PATCH 045/254] Tables: save/restore column ID (unused) Ditched UserID storage, which was provably broken/unused. --- imgui_internal.h | 7 ++++--- imgui_tables.cpp | 14 +++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 4b5e5be94..5904405e8 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2905,6 +2905,7 @@ struct ImGuiTableColumn float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). ImRect ClipRect; // Clipping rectangle for the column + ImGuiID ID; // Hash of column name (ignoring top of ID stack), used for .ini persistance when available. ImGuiID UserID; // Optional, value passed to TableSetupColumn() float WorkMinX; // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column float WorkMaxX; // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2) @@ -3140,18 +3141,18 @@ struct IMGUI_API ImGuiTableTempData struct ImGuiTableColumnSettings { float WidthOrWeight; - ImGuiID UserID; + ImGuiID ID; ImGuiTableColumnIdx Index; ImGuiTableColumnIdx DisplayOrder; ImGuiTableColumnIdx SortOrder; ImU8 SortDirection : 2; - ImS8 IsEnabled : 2; // "Visible" in ini file + ImS8 IsEnabled : 2; // "Visible" in .ini file ImU8 IsStretch : 1; ImGuiTableColumnSettings() { WidthOrWeight = 0.0f; - UserID = 0; + ID = 0; Index = -1; DisplayOrder = SortOrder = -1; SortDirection = ImGuiSortDirection_None; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2174d2164..409dd46d5 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1698,6 +1698,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo } TableSetupColumnFlags(table, column, flags); + column->ID = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0; column->UserID = user_id; flags = column->Flags; @@ -3811,6 +3812,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; + column_settings->ID = column->ID; column_settings->WidthOrWeight = width_or_weight; column_settings->Index = (ImGuiTableColumnIdx)n; column_settings->DisplayOrder = column->DisplayOrder; @@ -3993,12 +3995,13 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, char c = 0; ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; column->Index = (ImGuiTableColumnIdx)column_n; - if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } + if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); } // FIXME-LEGACY: Removed 2025/11/12, was never properly set. if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } + if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; } } } @@ -4026,16 +4029,16 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) { // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" - bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1); + bool save_column = save_size || save_visible || save_order || (save_sort && column->SortOrder != -1); if (!save_column) continue; buf->appendf("Column %-2d", column_n); - if (column->UserID != 0) { buf->appendf(" UserID=%08X", column->UserID); } if (save_size && column->IsStretch) { buf->appendf(" Weight=%.4f", column->WidthOrWeight); } if (save_size && !column->IsStretch) { buf->appendf(" Width=%d", (int)column->WidthOrWeight); } if (save_visible) { buf->appendf(" Visible=%d", column->IsEnabled); } if (save_order) { buf->appendf(" Order=%d", column->DisplayOrder); } if (save_sort && column->SortOrder != -1) { buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); } + if (column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } buf->append("\n"); } buf->append("\n"); @@ -4121,6 +4124,7 @@ void ImGui::TableGcCompactSettings() // [SECTION] Tables: Debugging //------------------------------------------------------------------------- // - DebugNodeTable() [Internal] +// - DebugNodeTableSettings() [Internal] //------------------------------------------------------------------------- #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -4219,10 +4223,10 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) { ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; - BulletText("Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X", + BulletText("Column %d Order %d SortOrder %2d %s Vis %d %s %7.3f ID 0x%08X", n, column_settings->DisplayOrder, column_settings->SortOrder, (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", - column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->UserID); + column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->ID); } TreePop(); } From d43c3f929ca3aabd548a760b272ea04426c21c50 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 8 Jun 2026 16:10:47 +0200 Subject: [PATCH 046/254] Tables: split code from TableSetupColumn() so it can be more easily deferred. --- imgui_tables.cpp | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 409dd46d5..5613f6448 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1669,17 +1669,9 @@ static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, // See "COLUMNS SIZING POLICIES" comments at the top of this file // If (init_width_or_weight <= 0.0f) it is ignored -void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16 name_offset, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) { - ImGuiContext& g = *GImGui; - ImGuiTable* table = g.CurrentTable; - IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); - IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!"); - IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); // Table layout is locked when submitting a row or when calling BeginMultiSelect() with box-select. - IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); - - ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; - table->DeclColumnsCount++; + ImGuiTableColumn* column = &table->Columns[idx]; // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa. // Give a grace to users of ImGuiTableFlags_ScrollX. @@ -1698,8 +1690,9 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo } TableSetupColumnFlags(table, column, flags); - column->ID = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0; + column->ID = id; column->UserID = user_id; + column->NameOffset = name_offset; flags = column->Flags; // Initialize defaults @@ -1711,15 +1704,29 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo init_flags |= ImGuiTableFlags_Resizable; TableInitColumnDefaults(table, column, init_flags); } +} + +void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); + IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!"); + IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); // Table layout is locked when submitting a row or when calling BeginMultiSelect() with box-select. + IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); // Store name (append with zero-terminator in contiguous buffer) // FIXME: If we recorded the number of \n in names we could compute header row height - column->NameOffset = -1; + ImS16 name_offset = -1; if (label != NULL && label[0] != 0) { - column->NameOffset = (ImS16)table->ColumnsNames.size(); + name_offset = (ImS16)table->ColumnsNames.size(); table->ColumnsNames.append(label, label + ImStrlen(label) + 1); } + + const ImGuiID column_id = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0; + TableSetupColumnApply(table, table->DeclColumnsCount, column_id, name_offset, flags, init_width_or_weight, user_id); + table->DeclColumnsCount++; } // [Public] From fea6f4603a9727b10f41d10789809a0b03123f1f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 10 Jun 2026 20:18:31 +0200 Subject: [PATCH 047/254] ImSpan: added clear(), empty() helpers. --- imgui_internal.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_internal.h b/imgui_internal.h index 5904405e8..4d87c4c29 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -690,6 +690,8 @@ struct ImSpan inline void set(T* data, int size) { Data = data; DataEnd = data + size; } inline void set(T* data, T* data_end) { Data = data; DataEnd = data_end; } + inline void clear() { Data = DataEnd = NULL; } + inline bool empty() const { return Data == DataEnd; } inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } inline int size_in_bytes() const { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); } inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } From 94a3dcc5fa7b83b0d559396679dd6e3cd9b5b4f6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 16:12:16 +0200 Subject: [PATCH 048/254] Tables: tweaked debug tools + minor data packing in in ImGuiTableColumn --- imgui.cpp | 2 +- imgui_internal.h | 6 +++--- imgui_tables.cpp | 39 +++++++++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index bbb29f550..2dbdc70c0 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16956,7 +16956,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) { for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) - DebugNodeTableSettings(settings); + DebugNodeTableSettings(settings, NULL); TreePop(); } diff --git a/imgui_internal.h b/imgui_internal.h index 4d87c4c29..da8122e63 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2934,8 +2934,8 @@ struct ImGuiTableColumn bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen). bool IsPreserveWidthAuto; ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte - ImU8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit - ImU8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem + ImU8 AutoFitQueue : 4; // Queue of 4 values for the next 4 frames to request auto-fit + ImU8 CannotSkipItemsQueue : 4; // Queue of 4 values for the next 4 frames to disable Clipped/SkipItem ImU8 SortDirection : 2; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending ImU8 SortDirectionsAvailCount : 2; // Number of available sort directions (0 to 3) ImU8 SortDirectionsAvailMask : 4; // Mask of available sort directions (1-bit each) @@ -3785,7 +3785,7 @@ namespace ImGui IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); - IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); + IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings, ImGuiTable* table); IMGUI_API void DebugNodeInputTextState(ImGuiInputTextState* state); IMGUI_API void DebugNodeTypingSelectState(ImGuiTypingSelectState* state); IMGUI_API void DebugNodeMultiSelectState(ImGuiMultiSelectState* state); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 5613f6448..2f9bd1970 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4214,28 +4214,39 @@ void ImGui::DebugNodeTable(ImGuiTable* table) } } if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) - DebugNodeTableSettings(settings); + DebugNodeTableSettings(settings, table); if (clear_settings) table->IsResetAllRequest = true; // Queue a call to TableResetSettings() TreePop(); } -void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) +void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings, ImGuiTable* table) { - if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount)) - return; - BulletText("SaveFlags: 0x%08X", settings->SaveFlags); - BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); - for (int n = 0; n < settings->ColumnsCount; n++) + if (settings->ID == 0) + PushID(settings); + const bool open = TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount); + const bool hovered = IsItemHovered(); + if (hovered && table == NULL && settings->ID != 0) + table = TableFindByID(settings->ID); + if (hovered && table != NULL) + GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + if (open) { - ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; - ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; - BulletText("Column %d Order %d SortOrder %2d %s Vis %d %s %7.3f ID 0x%08X", - n, column_settings->DisplayOrder, column_settings->SortOrder, - (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", - column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->ID); + BulletText("SaveFlags: 0x%08X", settings->SaveFlags); + BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); + for (int n = 0; n < settings->ColumnsCount; n++) + { + ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; + BulletText("Column %d Order %d SortOrder %2d %s Vis %d %s %7.3f ID 0x%08X", + n, column_settings->DisplayOrder, column_settings->SortOrder, + (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", + column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->ID); + } + TreePop(); } - TreePop(); + if (settings->ID == 0) + PopID(); } #else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS From 3240201de298d952a7bd41292c80fcc190dc4bf9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 16:37:26 +0200 Subject: [PATCH 049/254] Fixing build. --- imgui_tables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2f9bd1970..30fddf627 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4252,7 +4252,7 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings, ImGuiTable* tab #else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS void ImGui::DebugNodeTable(ImGuiTable*) {} -void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} +void ImGui::DebugNodeTableSettings(ImGuiTableSettings*, ImGuiTable* table) {} #endif From 2aaa6d8173bdc8837aa2ab98854b5e0152237e40 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 16:52:52 +0200 Subject: [PATCH 050/254] CI: use Visual Studio 2026. ref: https://github.com/actions/runner-images/blob/win25-vs2026/20260608.135/images/windows/Windows2025-VS2026-Readme.md --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bc81bf81..fd06b482c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,8 @@ jobs: working-directory: ${{ github.workspace }}/imgui env: - VS_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\ - MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\ + VS_PATH: C:\Program Files\Microsoft Visual Studio\2026\Enterprise\ + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2026\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v6 with: @@ -667,7 +667,7 @@ jobs: working-directory: ${{ github.workspace }}/imgui env: - MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\ + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2026\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v6 From 360183924b33195e1c3889cccc8c777e4dfa7502 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 17:06:14 +0200 Subject: [PATCH 051/254] CI: use Visual Studio 2026. (correct path) --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd06b482c..a4d3ed459 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,8 @@ jobs: working-directory: ${{ github.workspace }}/imgui env: - VS_PATH: C:\Program Files\Microsoft Visual Studio\2026\Enterprise\ - MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2026\Enterprise\MSBuild\Current\Bin\ + VS_PATH: C:\Program Files\Microsoft Visual Studio\18\Enterprise + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v6 with: @@ -667,7 +667,7 @@ jobs: working-directory: ${{ github.workspace }}/imgui env: - MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\2026\Enterprise\MSBuild\Current\Bin\ + MSBUILD_PATH: C:\Program Files\Microsoft Visual Studio\18\Enterprise\MSBuild\Current\Bin\ steps: - uses: actions/checkout@v6 From 721da6a34c220b53eaa6f2dec0105123f5a6ea37 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 18:14:28 +0200 Subject: [PATCH 052/254] Tables: apply queued requests in TableUpdateLayout() To allow calling TableQueueSetColumnDisplayOrder() between TableSetupColumn() and between TableUpdateLayout() # Conflicts: # imgui_tables.cpp --- imgui_internal.h | 2 +- imgui_tables.cpp | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index da8122e63..87de72045 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3197,7 +3197,7 @@ namespace ImGui IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count); - IMGUI_API void TableBeginApplyRequests(ImGuiTable* table); + IMGUI_API void TableApplyQueuedRequests(ImGuiTable* table); IMGUI_API void TableSetupDrawChannels(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 30fddf627..d49478de1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -40,14 +40,16 @@ Index of this file: // | TableBeginInitMemory() - first time table is used // | TableResetSettings() - on settings reset // | TableLoadSettings() - on settings load -// | TableBeginApplyRequests() - apply queued resizing/reordering/hiding requests -// | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) -// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width //----------------------------------------------------------------------------- // - TableSetupColumn() user submit columns details (optional) // - TableSetupScrollFreeze() user submit scroll freeze information (optional) //----------------------------------------------------------------------------- // - TableUpdateLayout() [Internal] followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow(). +// | TableLoadSettingsForColumns() - on settings load +// | TableApplyQueuedRequests() - apply queued resizing/reordering/hiding requests +// | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) +// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width +// | - TableSetColumnDisplayOrder() - apply reordering a column // | TableSetupDrawChannels() - setup ImDrawList channels // | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission // | TableBeginContextMenuPopup() @@ -253,7 +255,7 @@ Index of this file: // - BeginTable() // - BeginTableEx() [Internal] // - TableBeginInitMemory() [Internal] -// - TableBeginApplyRequests() [Internal] +// - TableApplyQueuedRequests() [Internal] // - TableSetupColumnFlags() [Internal] // - TableUpdateLayout() [Internal] // - TableUpdateBorders() [Internal] @@ -626,10 +628,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn() if (table->ColumnsNames.Buf.Size > 0) table->ColumnsNames.Buf.resize(0); - - // Apply queued resizing/reordering/hiding requests - TableBeginApplyRequests(table); - + return true; } @@ -664,7 +663,7 @@ void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count) } // Apply queued resizing/reordering/hiding requests -void ImGui::TableBeginApplyRequests(ImGuiTable* table) +void ImGui::TableApplyQueuedRequests(ImGuiTable* table) { // Handle resizing request // (We process this in the TableBegin() of the first instance of each table) @@ -845,6 +844,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) ImGuiContext& g = *GImGui; IM_ASSERT(table->IsLayoutLocked == false); + // Apply queued resizing/reordering/hiding requests + TableApplyQueuedRequests(table); + // Handle DPI/font resize // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. From e6147a3993dab107791627446f18f32678b76978 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 18:17:23 +0200 Subject: [PATCH 053/254] Tables: allow calling TableQueueSetColumnDisplayOrder() between TableSetupColumn() and TableUpdateLayout() by removing an early out. As `table->Columns[column_n].DisplayOrder` is not viable in this scope during a reconcile. --- imgui_tables.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d49478de1..3dcc396bd 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -775,8 +775,9 @@ void ImGui::TableQueueSetColumnDisplayOrder(ImGuiTable* table, int column_n, int table->ReorderColumn = (ImGuiTableColumnIdx)column_n; table->ReorderColumnDstOrder = (ImGuiTableColumnIdx)-1; dst_order = TableGetMaxDisplayOrderAllowed(table, src_order, dst_order); - if (dst_order != src_order) - table->ReorderColumnDstOrder = (ImGuiTableColumnIdx)dst_order; + if (table->IsLayoutLocked && dst_order == src_order) // We allow calling the function before layout w/ reconcile so don't early out. + return; + table->ReorderColumnDstOrder = (ImGuiTableColumnIdx)dst_order; } // Adjust flags: default width mode + stretch columns are not allowed when auto extending From b46a37eac92e5f64683be44b11dc159727fbc208 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 18:40:42 +0200 Subject: [PATCH 054/254] Tables: comments about misleading TableSetupColumn() user_id parameter, renamed to user_data. ImGuiTableColumnSortSpecs::ColumnUserID should be changed to ColumnUserData. --- imgui.h | 7 ++++--- imgui_internal.h | 4 ++-- imgui_tables.cpp | 16 ++++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/imgui.h b/imgui.h index 88419d6f8..cbfcdc8b8 100644 --- a/imgui.h +++ b/imgui.h @@ -909,14 +909,15 @@ namespace ImGui IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible. // Tables: Headers & Columns declaration - // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc. + // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, various other flags etc. + // (the trailing 'ImGuiID user_data', which used to be referred to as 'ImGuiID user_id', is merely user data that is blindly copied in ImGuiTableColumnSortSpecs). // - Use TableHeadersRow() to create a header row and automatically submit a TableHeader() for each column. // Headers are required to perform: reordering, sorting, and opening the context menu. // The context menu can also be made available in columns body using ImGuiTableFlags_ContextMenuInBody. // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in // some advanced use cases (e.g. adding custom widgets in header row). // - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled. When freezing columns you would usually also use ImGuiTableColumnFlags_NoHide on them. - IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_id = 0); + IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_data = 0); IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) IMGUI_API void TableHeadersRow(); // submit a row with headers cells based on data provided to TableSetupColumn() + submit context menu @@ -2159,7 +2160,7 @@ struct ImGuiTableSortSpecs // Sorting specification for one column of a table (sizeof == 12 bytes) struct ImGuiTableColumnSortSpecs { - ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) + ImGuiID ColumnUserID; // User data for the column (if specified by a TableSetupColumn() call in the 'ImGuiID user_data' field). FIXME: Should be called 'UserData'.. ImS16 ColumnIndex; // Index of the column ImS16 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) ImGuiSortDirection SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending diff --git a/imgui_internal.h b/imgui_internal.h index 87de72045..1b5c09ab2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2907,8 +2907,8 @@ struct ImGuiTableColumn float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). ImRect ClipRect; // Clipping rectangle for the column - ImGuiID ID; // Hash of column name (ignoring top of ID stack), used for .ini persistance when available. - ImGuiID UserID; // Optional, value passed to TableSetupColumn() + ImGuiID ID; // Hash of column name (ignoring top of ID stack), used for .ini persistence when available. + ImGuiID UserData; // (Optional) User data value passed to TableSetupColumn() float WorkMinX; // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column float WorkMaxX; // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2) float ItemWidth; // Current item width for the column, preserved across rows diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3dcc396bd..50bb3dba9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -893,7 +893,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None); column->NameOffset = -1; - column->UserID = 0; + column->UserData = 0; column->InitStretchWeightOrWidth = -1.0f; } @@ -1672,7 +1672,7 @@ static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, // See "COLUMNS SIZING POLICIES" comments at the top of this file // If (init_width_or_weight <= 0.0f) it is ignored -static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16 name_offset, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16 name_offset, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data) { ImGuiTableColumn* column = &table->Columns[idx]; @@ -1694,7 +1694,7 @@ static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16 TableSetupColumnFlags(table, column, flags); column->ID = id; - column->UserID = user_id; + column->UserData = user_data; column->NameOffset = name_offset; flags = column->Flags; @@ -1709,7 +1709,7 @@ static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16 } } -void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data_for_sort_specs) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; @@ -1728,7 +1728,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo } const ImGuiID column_id = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0; - TableSetupColumnApply(table, table->DeclColumnsCount, column_id, name_offset, flags, init_width_or_weight, user_id); + TableSetupColumnApply(table, table->DeclColumnsCount, column_id, name_offset, flags, init_width_or_weight, user_data_for_sort_specs); table->DeclColumnsCount++; } @@ -3105,7 +3105,7 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) continue; IM_ASSERT(column->SortOrder < table->SortSpecsCount); ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder]; - sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnUserID = column->UserData; sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; sort_spec->SortDirection = (ImGuiSortDirection)column->SortDirection; @@ -4198,13 +4198,13 @@ void ImGui::DebugNodeTable(ImGuiTable* table) "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n" "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n" "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n" - "Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s..", + "Sort: %d%s, UserData: 0x%08X, Flags: 0x%04X: %s%s%s..", n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, (n < table->FreezeColumnsRequest) ? " (Frozen)" : "", column->IsEnabled, column->IsVisibleX, column->IsVisibleY, column->IsRequestOutput, column->IsSkipItems, column->DrawChannelFrozen, column->DrawChannelUnfrozen, column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight, column->StretchWeight > 0.0f ? (column->StretchWeight / sum_weights) * 100.0f : 0.0f, column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x, column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX, - column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" : "", column->UserID, column->Flags, + column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" : "", column->UserData, column->Flags, (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); From 138c0e6c7dd22e45cb3d90de1c15ebf555899574 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2026 19:32:56 +0200 Subject: [PATCH 055/254] Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. --- docs/CHANGELOG.txt | 2 ++ imconfig.h | 2 +- imgui_internal.h | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index eb1e84202..17a4b060a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -80,6 +80,8 @@ Other Changes: - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). +- Misc: + - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. diff --git a/imconfig.h b/imconfig.h index 0a4ca08af..851a44b1b 100644 --- a/imconfig.h +++ b/imconfig.h @@ -55,7 +55,7 @@ //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Enable Test Engine / Automation features. -//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. +//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_imconfig.h", see Test Engine for details. //---- Include imgui_user.h at the end of imgui.h as a convenience // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. diff --git a/imgui_internal.h b/imgui_internal.h index 1b5c09ab2..e6644799e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -322,6 +322,8 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xde01") #elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__) #define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0") +#elif defined(__GNUC__) && defined(__aarch64__) +#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xd4200000") // GDB needs 'set $pc=($pc+4)' to skip this :( #else #define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger! #endif From b47a3898b4b6cff28e13f8c268c179708f07ccad Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Wed, 17 Jun 2026 06:51:54 -0400 Subject: [PATCH 056/254] Fixed unused parameter 'table'. (#9447, #9446) --- imgui_tables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 50bb3dba9..4459340fe 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4255,7 +4255,7 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings, ImGuiTable* tab #else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS void ImGui::DebugNodeTable(ImGuiTable*) {} -void ImGui::DebugNodeTableSettings(ImGuiTableSettings*, ImGuiTable* table) {} +void ImGui::DebugNodeTableSettings(ImGuiTableSettings*, ImGuiTable*) {} #endif From 2064059a7f6b56f894429f26e605005cdc10ae2e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2026 16:09:04 +0200 Subject: [PATCH 057/254] Docs, Backends: OpenGL3, Vulkan: added comments about custom sampler. (#9378) --- backends/imgui_impl_opengl3.cpp | 3 ++- backends/imgui_impl_vulkan.cpp | 13 +++++++++++-- backends/imgui_impl_vulkan.h | 1 + docs/CHANGELOG.txt | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index a9e32b7ac..b3d32426a 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -25,6 +25,7 @@ // (minor and older changes stripped away, please see git history for details) // 2026-06-23: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) +// (Breaking): this change prioritize using glBindSampler() when available, which would override glTexParameter() settings you may have set on custom textures. // 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. @@ -549,7 +550,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); // Emulate sampler change (even though it is technically part of texture data) - // As a sort of hack/workaround, we only start writing using glTextParameter() if sampler is ever changed explicitly. + // As a sort of hack/workaround, we only start writing using glTexParameter() if sampler is ever changed explicitly. if (!bd->HasBindSampler && bd->UseTexParameterToSetSampler) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, bd->NextSampler); diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 3baf3b752..ef7dd625f 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -537,8 +537,17 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline // Draw callbacks static void ImGui_ImplVulkan_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. -static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } -static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } +static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->RenderState->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } +static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->RenderState->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } + +// If you want to use your own sampler, you can create your own callback, access ImGui_ImplVulkan_RenderState for command-buffer and pipeline layout, e.g. +/* +void ImGui_ImplVulkan_DrawCallback_SetSamplerCustom(const ImDrawList*, const ImDrawCmd* cmd) +{ + ImGui_ImplVulkan_RenderState* render_state = (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; + vkCmdBindDescriptorSets(render_state->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, render_state->PipelineLayout, 1, 1, (VkDescriptorSet*)cmd->UserCallbackData, 0, nullptr); +} +*/ // Render function void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline) diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 603f39474..cf6a3be15 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -167,6 +167,7 @@ IMGUI_IMPL_API bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_vers // [BETA] Selected render state data shared with callbacks. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplVulkan_RenderDrawData() call. +// ImGui_ImplVulkan_RenderState* render_state = (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; // (Please open an issue if you feel you need access to more data) struct ImGui_ImplVulkan_RenderState { diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 17a4b060a..dde360912 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -142,6 +142,8 @@ Breaking Changes: - DirectX10, DirectX11, SDLGPU3, Vulkan: removed samplers from `ImGui_ImplXXXX_RenderState`. Prefer to use backend-agnostic DrawCallback_SetSamplerLinear which works everywhere! (#9378) If there is a legit need/request for them or any render state we can always add them back. + - OpenGL3: the new sampler support system prioritize using glBindSampler() when available, + which would override glTexParameter() settings you may have set on custom textures. (#9378) Other Changes: From 13fc81fc8235025481f8f50da6e2166e3657a17f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2026 12:49:52 +0200 Subject: [PATCH 058/254] Tables: fixes incorrect forward declaration for a missing function + fixed tooltip comment. (#9445, #9448) --- imgui_internal.h | 1 - imgui_tables.cpp | 1 - imgui_widgets.cpp | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index e6644799e..ffd9d7658 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3236,7 +3236,6 @@ namespace ImGui // Tables: Settings IMGUI_API void TableLoadSettings(ImGuiTable* table); - IMGUI_API void TableLoadSettingsForColumns(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table); IMGUI_API void TableResetSettings(ImGuiTable* table); IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 4459340fe..12015ea99 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -45,7 +45,6 @@ Index of this file: // - TableSetupScrollFreeze() user submit scroll freeze information (optional) //----------------------------------------------------------------------------- // - TableUpdateLayout() [Internal] followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow(). -// | TableLoadSettingsForColumns() - on settings load // | TableApplyQueuedRequests() - apply queued resizing/reordering/hiding requests // | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) // | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 4ea116234..7b8c4e9ce 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1577,7 +1577,7 @@ bool ImGui::TextLinkOpenURL(const char* label, const char* url) bool pressed = TextLink(label); if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL) g.PlatformIO.Platform_OpenInShellFn(&g, url); - SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label + SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when it is the same as the label. if (BeginPopupContextItem()) { if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink))) From 7aaf10f15b9b637af582fdc9672d4d5996660725 Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Wed, 17 Jun 2026 08:11:19 -0400 Subject: [PATCH 059/254] Docs: fixed typos (#9449) --- imgui.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index cbfcdc8b8..ff9f0d9d7 100644 --- a/imgui.h +++ b/imgui.h @@ -649,7 +649,7 @@ namespace ImGui // Widgets: Images // - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. - // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. + // - Image() adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. // - An obsolete version of Image(), before 1.91.9 (March 2025), had a 'tint_col' parameter which is now supported by the ImageWithBg() function. IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); @@ -809,7 +809,7 @@ namespace ImGui // - Use BeginMenuBar() on a window ImGuiWindowFlags_MenuBar to append to its menu bar. // - Use BeginMainMenuBar() to create a menu bar at the top of the screen and append to it. // - Use BeginMenu() to create a menu. You can call BeginMenu() multiple time with the same identifier to append more items to it. - // - Not that MenuItem() keyboardshortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment. + // - Note that MenuItem() keyboard shortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment. IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. From 73a6610e810ab6209c7c8f0dec2209c72581235b Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2026 15:00:20 +0200 Subject: [PATCH 060/254] Backends: OpenGL3: Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) --- backends/imgui_impl_opengl3.cpp | 74 ++++++++++++++++++++++++++------- backends/imgui_impl_opengl3.h | 13 ++++++ docs/CHANGELOG.txt | 2 + 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index b3d32426a..1f96c6fe8 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -23,7 +23,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-06-23: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) +// 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. +// 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // (Breaking): this change prioritize using glBindSampler() when available, which would override glTexParameter() settings you may have set on custom textures. // 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) @@ -255,8 +256,6 @@ struct ImGui_ImplOpenGL3_Data bool HasBindSampler; bool HasClipOrigin; bool UseBufferSubData; - bool UseTexParameterToSetSampler; - GLuint NextSampler; // Used if !HasBindSampler && UseTexParameterToSetSampler. #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER GLuint TexSamplers[2]; // Used if HasBindSimpler. (0=linear, 1=nearest) #endif @@ -332,7 +331,7 @@ void ImGui_ImplOpenGL3_NewFrame() IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!"); } -static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, ImGui_ImplOpenGL3_RenderState* render_state, int fb_width, int fb_height, GLuint vertex_array_object) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -386,8 +385,11 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - if (bd->HasBindSampler) - glBindSampler(0, bd->TexSamplers[0]); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. + if (render_state->UseBindSampler) + { + render_state->CurrentSampler = bd->TexSamplers[0]; + glBindSampler(0, render_state->CurrentSampler); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. + } #endif (void)vertex_array_object; @@ -408,13 +410,42 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid // Draw callbacks static void ImGui_ImplOpenGL3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) +{ + ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER -static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); if (bd->HasBindSampler) { glBindSampler(0, bd->TexSamplers[0]); } else { bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } } -static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); if (bd->HasBindSampler) { glBindSampler(0, bd->TexSamplers[1]); } else { bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } } -#else -static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } -static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->HasBindSampler) + { + render_state->CurrentSampler = bd->TexSamplers[0]; + render_state->UseTexParameterFilter = false; + glBindSampler(0, render_state->CurrentSampler); + } + else #endif + { + render_state->UseTexParameterFilter = true; + render_state->CurrentTexParameterFilter = GL_LINEAR; + } +} +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) +{ + ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->HasBindSampler) + { + render_state->CurrentSampler = bd->TexSamplers[1]; + render_state->UseTexParameterFilter = false; + glBindSampler(0, render_state->CurrentSampler); + } + else +#endif + { + render_state->UseTexParameterFilter = true; + render_state->CurrentTexParameterFilter = GL_NEAREST; + } +} // OpenGL3 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. @@ -484,7 +515,17 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY GL_CALL(glGenVertexArrays(1, &vertex_array_object)); #endif - ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + + // Setup render state structure (for callbacks and custom texture bindings) + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + ImGui_ImplOpenGL3_RenderState render_state; + render_state.UseBindSampler = bd->HasBindSampler; + render_state.UseTexParameterFilter = false; + render_state.CurrentSampler = 0; + render_state.CurrentTexParameterFilter = 0; + platform_io.Renderer_RenderState = &render_state; + + ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports @@ -531,7 +572,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // User callback, registered via ImDrawList::AddCallback() if (pcmd->UserCallback == ImGui_ImplOpenGL3_DrawCallback_ResetRenderState) - ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object); else pcmd->UserCallback(draw_list, pcmd); } @@ -551,10 +592,10 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Emulate sampler change (even though it is technically part of texture data) // As a sort of hack/workaround, we only start writing using glTexParameter() if sampler is ever changed explicitly. - if (!bd->HasBindSampler && bd->UseTexParameterToSetSampler) + if (render_state.UseTexParameterFilter) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, bd->NextSampler); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, bd->NextSampler); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, render_state.CurrentTexParameterFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, render_state.CurrentTexParameterFilter); } #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET @@ -566,6 +607,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) } } } + platform_io.Renderer_RenderState = nullptr; // Destroy the temporary VAO #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h index aabe04b07..a93804213 100644 --- a/backends/imgui_impl_opengl3.h +++ b/backends/imgui_impl_opengl3.h @@ -65,4 +65,17 @@ IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex); #endif +// [BETA] Selected render state data shared with callbacks. +// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplOpenGL3_RenderDrawData() call. +// (Please open an issue if you feel you need access to more data) +struct ImGui_ImplOpenGL3_RenderState +{ + bool UseBindSampler; + bool UseTexParameterFilter; + unsigned int CurrentSampler; // (GLuint) Used if UseBindSampler == true, otherwise always 0 + unsigned int CurrentTexParameterFilter; // (GLuint) Used if UseTexParameterToSetSampler == true +}; + +static inline ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState() { return (ImGui_ImplOpenGL3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #endif // #ifndef IMGUI_DISABLE diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index dde360912..b962a1352 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -86,6 +86,8 @@ Other Changes: - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] + - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to + dynamically select between use of glBindSampler() and glTexParameter(). (#9378) - Win32: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends From 9a5c070308ae97bf0f884b071d0262ae1cad26f7 Mon Sep 17 00:00:00 2001 From: Cleroth Date: Sat, 13 Jun 2026 05:35:13 +0800 Subject: [PATCH 061/254] Windows: fixed double-click collapse toggle not owning the mouse button. (#9439) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index b962a1352..2238e8dac 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -52,6 +52,9 @@ Other Changes: for user code to bypass it without using a clickable item. (#9382) - Clicking on a window's empty-space to move/focus a window checks for lack of queued focus request. (#9382) + - Fixed double-click collapse toggle not owning the mouse button. If a + `SetNextWindowPos()` with pivot was queued in the same frame, the second + click could trigger another item in the same window. (#9439) [@Cleroth] - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. diff --git a/imgui.cpp b/imgui.cpp index 2dbdc70c0..e56165abf 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7714,7 +7714,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImRect title_bar_rect = window->TitleBarRect(); if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && g.ActiveId == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)) if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner) + { window->WantCollapseToggle = true; + SetKeyOwner(ImGuiKey_MouseLeft, window->MoveId); // Claim input the same way ButtonBehavior() does. Prevent a same-frame move from triggering other items. (#9439) + } if (window->WantCollapseToggle) { window->Collapsed = !window->Collapsed; From d15966ff6cb48adacaae2f6d40230b4194d8ea70 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2026 19:19:38 +0200 Subject: [PATCH 062/254] Tables: fixed TableGcCompactSettings() not invalidating bound offset. A crash could in theory be triggered if clicking "Gc All" after saved tables have reduced column count. Tested by "table_settings_5" --- imgui.h | 2 +- imgui_tables.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index ff9f0d9d7..720e3c8cd 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.9 WIP" -#define IMGUI_VERSION_NUM 19283 +#define IMGUI_VERSION_NUM 19284 #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_tables.cpp b/imgui_tables.cpp index 12015ea99..a028e3b33 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4110,7 +4110,7 @@ void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data) temp_data->LastTimeActive = -1.0f; } -// Compact and remove unused settings data (currently only used by TestEngine) +// Compact and remove unused or resize settings data (only TestEngine mark unused, but resizing table down can lead to compaction) void ImGui::TableGcCompactSettings() { ImGuiContext& g = *GImGui; @@ -4126,6 +4126,9 @@ void ImGui::TableGcCompactSettings() if (settings->ID != 0) memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount)); g.SettingsTables.swap(new_chunk_stream); + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) + if (ImGuiTable* table = g.Tables.TryGetMapData(table_n)) + table->SettingsOffset = -1; } From b2377d616d3cc8fefde9aecc516b7e8da6535fb9 Mon Sep 17 00:00:00 2001 From: Clownacy Date: Tue, 23 Jun 2026 21:02:35 +0100 Subject: [PATCH 063/254] Nav, Popups: fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard instead of _NavEnableGamepad. (#9454) cc: #8803, #9270 --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2238e8dac..9b34d83f6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -74,6 +74,9 @@ Other Changes: calling ClearFonts() during rendering. - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) +- Nav: + - Fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard + instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imgui.cpp b/imgui.cpp index e56165abf..56860dbd5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -14356,7 +14356,7 @@ static void ImGui::NavUpdateContextMenuRequest() ImGuiContext& g = *GImGui; g.NavOpenContextMenuItemId = g.NavOpenContextMenuWindowId = 0; const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0; if ((!nav_keyboard_active && !nav_gamepad_active) || g.NavWindow == NULL) return; From 2f94e7dc13046715abb570891f54e2c3198e39fc Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 8 Jun 2026 17:40:05 +0200 Subject: [PATCH 064/254] Tables: add support for ImGuiTableFlags_TrackTopologyChanges (#9108, #4046) --- imgui.cpp | 3 + imgui.h | 1 + imgui_internal.h | 31 +++++++- imgui_tables.cpp | 196 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 181 insertions(+), 50 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 56860dbd5..c45606863 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5751,8 +5751,11 @@ void ImGui::NewFrame() if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time) TableGcCompactTransientBuffers(g.Tables.GetByIndex(i)); for (ImGuiTableTempData& table_temp_data : g.TablesTempData) + { + table_temp_data.ReconcileColumnsRequests.clear(); // Unusual: clear every frame because this is rarely used. if (table_temp_data.LastTimeActive >= 0.0f && table_temp_data.LastTimeActive < memory_compact_start_time) TableGcCompactTransientBuffers(&table_temp_data); + } if (g.GcCompactAll) GcCompactTransientMiscBuffers(); g.GcCompactAll = false; diff --git a/imgui.h b/imgui.h index 720e3c8cd..e4a8d0e07 100644 --- a/imgui.h +++ b/imgui.h @@ -2077,6 +2077,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). // Miscellaneous ImGuiTableFlags_HighlightHoveredColumn = 1 << 28, // Highlight column headers when hovered (may evolve into a fuller highlight) + ImGuiTableFlags_TrackTopologyChanges = 1 << 29, // [BETA] Saved columns data keyed by their identifier. Allow data to persist after column addition/deletion/reordering. Requires valid identifier for all columns. // [Internal] Combinations and masks ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame, diff --git a/imgui_internal.h b/imgui_internal.h index ffd9d7658..ff66e2989 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2893,7 +2893,7 @@ struct IMGUI_API ImGuiTabBar #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. #define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx. -// [Internal] sizeof() ~ 112 +// [Internal] sizeof() ~ 120 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. // We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping. // This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped". @@ -2935,6 +2935,8 @@ struct ImGuiTableColumn bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not. bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen). bool IsPreserveWidthAuto; + bool IsJustCreated; + bool IsLoadedSettings; ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte ImU8 AutoFitQueue : 4; // Queue of 4 values for the next 4 frames to request auto-fit ImU8 CannotSkipItemsQueue : 4; // Queue of 4 values for the next 4 frames to disable Clipped/SkipItem @@ -2952,10 +2954,28 @@ struct ImGuiTableColumn PrevEnabledColumn = NextEnabledColumn = -1; SortOrder = -1; SortDirection = ImGuiSortDirection_None; + IsJustCreated = true; DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1; } }; +// Passed to TableSetupColumn() +// sizeof() ~ 24+120 bytes +struct ImGuiTableReconcileColumnData +{ + // Setup data + ImGuiID ID; + ImS16 NameOffset; + ImGuiTableColumnFlags Flags; + float InitWidthOrWeight; + ImGuiID UserData; + + // Reconcile data + ImGuiTableColumnIdx ColumnNewIdx; + ImGuiTableColumnIdx ColumnOldIdx; + ImGuiTableColumn ColumnOldData; // Full backup of the column. Could be avoided by storing 1 of them and applying reconcile in the right order. Not worth bothering. +}; + // Transient cell data stored per row. // sizeof() ~ 6 bytes struct ImGuiTableCellData @@ -3117,7 +3137,7 @@ struct IMGUI_API ImGuiTable // - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure. // - We also leave out of this structure data that tend to be particularly useful for debugging/metrics. // FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs. -// sizeof() ~ 136 bytes. +// sizeof() ~ 176 bytes. struct IMGUI_API ImGuiTableTempData { ImGuiID WindowID; // Shortcut to g.Tables[TableIndex]->OuterWindow->ID. @@ -3126,6 +3146,11 @@ struct IMGUI_API ImGuiTableTempData float AngledHeadersExtraWidth; // Used in EndTable() ImVector AngledHeadersRequests; // Used in TableAngledHeadersRow() // FIXME: Single instance is enough? + // Topology change + ImVector ReconcileColumnsRequests; // Used in TableSetupColumn(), TableUpdateLayout(). Cleared every frame. + void* OldColumnsRawData; // Used in BeginTable() -> TableUpdateLayout() when resizing. + ImSpan OldColumnsData; + ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() ImDrawListSplitter DrawSplitter; @@ -3201,6 +3226,7 @@ namespace ImGui IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count); IMGUI_API void TableApplyQueuedRequests(ImGuiTable* table); IMGUI_API void TableSetupDrawChannels(ImGuiTable* table); + IMGUI_API void TableReconcileColumns(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); @@ -3214,6 +3240,7 @@ namespace ImGui IMGUI_API void TableFixDisplayOrder(ImGuiTable* table); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); + IMGUI_API void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask); IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column); IMGUI_API void TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column); IMGUI_API float TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a028e3b33..01a03e4d3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -363,6 +363,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG g.TablesTempData.resize(g.TablesTempDataStacked, ImGuiTableTempData()); ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempData[g.TablesTempDataStacked - 1]; temp_data->TableIndex = table_idx; + temp_data->ReconcileColumnsRequests.resize(0); // FIXME: Use shrink(0) everywhere? table->DrawSplitter = &table->TempData->DrawSplitter; table->DrawSplitter->Clear(); @@ -563,15 +564,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->MemoryCompacted = false; // Setup memory buffer (clear data if columns count changed) - ImGuiTableColumn* old_columns_to_preserve = NULL; - void* old_columns_raw_data = NULL; const int old_columns_count = table->Columns.size(); if (old_columns_count != 0 && old_columns_count != columns_count) { - // Attempt to preserve width and other settings on column count/specs change (#4046) + // Attempt to preserve width and other settings on column count/specs change (#4046, #9108) IMGUI_DEBUG_LOG_TABLE("[table] Table 0x%08X column count %d -> %d, recreating storage.\n", table->ID, old_columns_count, columns_count); - old_columns_to_preserve = table->Columns.Data; - old_columns_raw_data = table->RawData; // Free at end of function + IM_ASSERT(temp_data->OldColumnsRawData == NULL); + temp_data->OldColumnsRawData = table->RawData; // Freed during layout + temp_data->OldColumnsData = table->Columns; table->RawData = NULL; } if (table->RawData == NULL) @@ -595,9 +595,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG for (int n = 0; n < columns_count; n++) { ImGuiTableColumn* column = &table->Columns[n]; - if (old_columns_to_preserve && n < old_columns_count) + if (temp_data->OldColumnsData.Data && n < temp_data->OldColumnsData.size()) { - *column = old_columns_to_preserve[n]; + *column = temp_data->OldColumnsData[n]; } else { @@ -611,8 +611,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->DisplayOrderToIndex[n] = column->DisplayOrder; } } - if (old_columns_raw_data) - IM_FREE(old_columns_raw_data); // Load settings if (table->IsSettingsRequestLoad) @@ -842,7 +840,41 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I void ImGui::TableUpdateLayout(ImGuiTable* table) { ImGuiContext& g = *GImGui; + ImGuiTableTempData* temp_data = table->TempData; IM_ASSERT(table->IsLayoutLocked == false); + const int columns_count = table->ColumnsCount; + + // Reconcile moved columns + if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) + { + IM_ASSERT_USER_ERROR(table->DeclColumnsCount == columns_count, "When using TrackTopologyChanges: must call TableSetupColumn() with a valid identifier for all columns!"); + if (temp_data->ReconcileColumnsRequests.Size > 0) + TableReconcileColumns(table); + } + if (temp_data->OldColumnsRawData) + { + IM_FREE(temp_data->OldColumnsRawData); + temp_data->OldColumnsRawData = NULL; + temp_data->OldColumnsData.clear(); + } + + // Apply columns settings + if (table->IsSettingsRequestLoad) + TableLoadSettingsForColumns(table); + if (table->IsInitializing || table->IsSettingsRequestLoad) + { + for (ImGuiTableColumn& column : table->Columns) + { + ImGuiTableFlags init_flags; + if (table->IsSettingsRequestLoad) + init_flags = column.IsLoadedSettings ? ~table->SettingsLoadedFlags : ~0; + else + init_flags = column.IsJustCreated ? ~0 : 0; + TableInitColumnDefaults(table, &column, init_flags); + } + TableFixDisplayOrder(table); // Call even for non _Reorderable table as we loaded .ini data. + table->IsSettingsRequestLoad = false; + } // Apply queued resizing/reordering/hiding requests TableApplyQueuedRequests(table); @@ -853,7 +885,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? - const int columns_count = table->ColumnsCount; if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) { const float scale_factor = new_ref_scale_unit / table->RefScale; @@ -892,7 +923,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None); column->NameOffset = -1; - column->UserData = 0; + column->ID = column->UserData = 0; column->InitStretchWeightOrWidth = -1.0f; } @@ -905,6 +936,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsSettingsDirty = true; } column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0; + column->IsJustCreated = false; if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1)) table->IsDefaultVisibility = false; @@ -1644,9 +1676,9 @@ void ImGui::EndTable() NavUpdateCurrentWindowIsScrollPushableX(); } -// Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings. +// Called in TableUpdateLayout() when initializing/after loading settings. // 'init_mask' specify which fields to initialize. -static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask) +void ImGui::TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask) { ImGuiTableColumnFlags flags = column->Flags; if (init_mask & ImGuiTableFlags_Resizable) @@ -1658,7 +1690,7 @@ static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, column->AutoFitQueue = 0x00; } if (init_mask & ImGuiTableFlags_Reorderable) - column->DisplayOrder = (ImGuiTableColumnIdx)table->Columns.index_from_ptr(column); + column->DisplayOrder = (ImGuiTableColumnIdx)((table->Flags & ImGuiTableFlags_Reorderable) ? -1 : table->Columns.index_from_ptr(column)); if (init_mask & ImGuiTableFlags_Hideable) column->IsUserEnabled = column->IsUserEnabledNextFrame = (flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1; if (init_mask & ImGuiTableFlags_Sortable) @@ -1695,20 +1727,10 @@ static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16 column->ID = id; column->UserData = user_data; column->NameOffset = name_offset; - flags = column->Flags; - - // Initialize defaults column->InitStretchWeightOrWidth = init_width_or_weight; - if (table->IsInitializing) - { - ImGuiTableFlags init_flags = ~table->SettingsLoadedFlags; - if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) - init_flags |= ImGuiTableFlags_Resizable; - TableInitColumnDefaults(table, column, init_flags); - } } -void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data_for_sort_specs) +void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; @@ -1725,10 +1747,63 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo name_offset = (ImS16)table->ColumnsNames.size(); table->ColumnsNames.append(label, label + ImStrlen(label) + 1); } - const ImGuiID column_id = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0; - TableSetupColumnApply(table, table->DeclColumnsCount, column_id, name_offset, flags, init_width_or_weight, user_data_for_sort_specs); - table->DeclColumnsCount++; + + // When ID changed or a column moved: defer the request until layout where we will process full reconcile. + const int column_idx = table->DeclColumnsCount++; + if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) + { + // On column count change: search the previous columns: + // - the live Columns[] array is truncated on shrink, so a moved column may only exist in old data. + // - the live Columns[] array may already be partially reset by the TableInitColumnDefaults() call above. + // When count changed, even a never-updated slot (prev_column_id == 0 while initializing) may have old data to recover. + // Defer applying data, TableUpdateLayout() will call TableSetupColumnApply(). + IM_ASSERT_USER_ERROR(column_id != 0, "When using TrackTopologyChanges: Must call TableSetupColumn() with a valid identifier for all columns!"); + ImGuiTableColumn* column = &table->Columns[column_idx]; + const bool may_have_old_data = (column->ID != 0 || !table->IsInitializing || !table->TempData->OldColumnsData.empty()); + if (column->ID != column_id && column_id != 0 && may_have_old_data) + { + table->TempData->ReconcileColumnsRequests.push_back(ImGuiTableReconcileColumnData()); + ImGuiTableReconcileColumnData& reconcile_data = table->TempData->ReconcileColumnsRequests.back(); + reconcile_data.ID = column_id; + reconcile_data.NameOffset = name_offset; + reconcile_data.Flags = flags; + reconcile_data.InitWidthOrWeight = init_width_or_weight; + reconcile_data.UserData = user_data; + reconcile_data.ColumnNewIdx = (ImGuiTableColumnIdx)column_idx; + reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)-1; + ImSpan& search_columns = table->TempData->OldColumnsData.empty() ? table->Columns : table->TempData->OldColumnsData; + for (ImGuiTableColumn& old_column : search_columns) + if (old_column.ID == column_id) + { + reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)search_columns.index_from_ptr(&old_column); + reconcile_data.ColumnOldData = old_column; + break; + } + column->NameOffset = name_offset; // Allow TableGetColumnName() to work before layout. + return; + } + } + + TableSetupColumnApply(table, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data); +} + +void ImGui::TableReconcileColumns(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImGuiTableTempData* temp_data = table->TempData; + IM_UNUSED(g); + IMGUI_DEBUG_LOG_TABLE("[table] Reconcile moved columns for table 0x%08X\n", table->ID); + for (ImGuiTableReconcileColumnData& reconcile_data : temp_data->ReconcileColumnsRequests) + { + ImGuiTableColumn* column = &table->Columns[reconcile_data.ColumnNewIdx]; + *column = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data. + TableSetupColumnApply(table, reconcile_data.ColumnNewIdx, reconcile_data.ID, reconcile_data.NameOffset, reconcile_data.Flags, reconcile_data.InitWidthOrWeight, reconcile_data.UserData); + IMGUI_DEBUG_LOG_TABLE("[table] - old %d -> new %d \"%s\"\n", reconcile_data.ColumnOldIdx, reconcile_data.ColumnNewIdx, TableGetColumnName(table, reconcile_data.ColumnNewIdx)); // Log at the end so NameOffset was copied. + } + TableFixDisplayOrder(table); + table->IsSettingsDirty = true; // FIXME-RECONCILE: Necessary? + temp_data->ReconcileColumnsRequests.resize(0); // GC-ed once in NewFrame() } // [Public] @@ -3778,9 +3853,7 @@ ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) ImGuiContext& g = *GImGui; ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); IM_ASSERT(settings->ID == table->ID); - if (settings->ColumnsCountMax >= table->ColumnsCount) - return settings; // OK - settings->ID = 0; // Invalidate storage, we won't fit because of a count change + return settings; } return NULL; } @@ -3803,6 +3876,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table) // Bind or create settings data ImGuiContext& g = *GImGui; ImGuiTableSettings* settings = TableGetBoundSettings(table); + if (settings != NULL && table->ColumnsCount > settings->ColumnsCountMax) + { + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + settings = NULL; + } if (settings == NULL) { settings = TableSettingsCreate(table->ID, table->ColumnsCount); @@ -3818,6 +3896,8 @@ void ImGui::TableSaveSettings(ImGuiTable* table) bool save_ref_scale = false; settings->SaveFlags = ImGuiTableFlags_None; + if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) + settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; @@ -3853,9 +3933,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table) void ImGui::TableLoadSettings(ImGuiTable* table) { ImGuiContext& g = *GImGui; - table->IsSettingsRequestLoad = false; if (table->Flags & ImGuiTableFlags_NoSavedSettings) + { + table->IsSettingsRequestLoad = false; // Done return; + } // Bind settings ImGuiTableSettings* settings; @@ -3875,30 +3957,50 @@ void ImGui::TableLoadSettings(ImGuiTable* table) table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; + // (TableUpdateLayout() will further read from settings data) +} - // Initialize default columns settings - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - { - ImGuiTableColumn* column = &table->Columns[column_n]; - TableInitColumnDefaults(table, column, ~0); - column->AutoFitQueue = 0x00; - } +void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) +{ + for (ImGuiTableColumn& column : table->Columns) + column.IsLoadedSettings = false; + ImGuiTableSettings* settings = TableGetBoundSettings(table); + if (settings == NULL) + return; + + // When using TrackTopologyChanges mode, columns ID are either all sets, either all cleared (initially). + //const bool has_columns_id = table->Columns[0].ID != 0; // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) { int column_n = column_settings->Index; + if ((table->Flags & ImGuiTableFlags_TrackTopologyChanges) && (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges))// && has_columns_id) + if (column_n >= table->ColumnsCount || table->Columns[column_n].ID != column_settings->ID) + { + // - Case 1: done during initial table creation: we don't have ID yet. Write back loaded IDs, will reconcile in TableUpdateLayout(). + // - Case 2 (this code): we have columns identifiers already. + column_n = -1; + for (int other_n = 0; other_n < table->ColumnsCount && column_n == -1; other_n++) + if (table->Columns[other_n].ID == column_settings->ID) + column_n = other_n; + } + if (column_n < 0 || column_n >= table->ColumnsCount) continue; ImGuiTableColumn* column = &table->Columns[column_n]; + column->IsLoadedSettings = true; + if (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges) + column->ID = column_settings->ID; if (settings->SaveFlags & ImGuiTableFlags_Resizable) { if (column_settings->IsStretch) column->StretchWeight = column_settings->WidthOrWeight; else column->WidthRequest = column_settings->WidthOrWeight; + column->AutoFitQueue = 0x00; } if (settings->SaveFlags & ImGuiTableFlags_Reorderable) column->DisplayOrder = column_settings->DisplayOrder; @@ -3907,12 +4009,6 @@ void ImGui::TableLoadSettings(ImGuiTable* table) column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } - - // Fix display order and build index - if (settings->SaveFlags & ImGuiTableFlags_Reorderable) - TableFixDisplayOrder(table); - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; } struct ImGuiTableFixDisplayOrderColumnData @@ -3927,7 +4023,8 @@ static int IMGUI_CDECL TableFixDisplayOrderComparer(const void* lhs, const void* const ImGuiTable* table = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Table; const ImGuiTableColumnIdx lhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Idx; const ImGuiTableColumnIdx rhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)rhs)->Idx; - const int order_delta = (table->Columns[lhs_idx].DisplayOrder - table->Columns[rhs_idx].DisplayOrder); + // Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after. + const int order_delta = ((unsigned short)table->Columns[lhs_idx].DisplayOrder - (unsigned short)table->Columns[rhs_idx].DisplayOrder); return (order_delta > 0) ? +1 : (order_delta < 0) ? -1 : (lhs_idx > rhs_idx) ? +1 : -1; } @@ -3945,6 +4042,8 @@ void ImGui::TableFixDisplayOrder(ImGuiTable* table) ImQsort(fdo_columns, (size_t)table->ColumnsCount, sizeof(ImGuiTableFixDisplayOrderColumnData), TableFixDisplayOrderComparer); for (int n = 0; n < table->ColumnsCount; n++) table->Columns[fdo_columns[n].Idx].DisplayOrder = (ImGuiTableColumnIdx)n; + for (int n = 0; n < table->ColumnsCount; n++) + table->DisplayOrderToIndex[table->Columns[n].DisplayOrder] = (ImGuiTableColumnIdx)n; } static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) @@ -4010,7 +4109,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } - if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; } + if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges; } } } @@ -4028,6 +4127,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; + const bool save_id = (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges) != 0; // We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings". buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve @@ -4047,7 +4147,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle if (save_visible) { buf->appendf(" Visible=%d", column->IsEnabled); } if (save_order) { buf->appendf(" Order=%d", column->DisplayOrder); } if (save_sort && column->SortOrder != -1) { buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); } - if (column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } + if (save_id && column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } buf->append("\n"); } buf->append("\n"); From f2fcdfe97fd6aa1db10af30b14c9a0ae069a4454 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2026 18:02:17 +0200 Subject: [PATCH 065/254] Tables: fixed duplicate settings entry. --- imgui_tables.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 01a03e4d3..bfa1dd26d 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -375,6 +375,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG const int previous_frame_active = table->LastFrameActive; const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1; const ImGuiTableFlags previous_flags = table->Flags; + const bool is_new_table = (previous_frame_active == -1); table->ID = id; table->Flags = flags; table->LastFrameActive = g.FrameCount; @@ -584,7 +585,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsInitializing) { // Initialize - table->SettingsOffset = -1; + if (is_new_table) + table->SettingsOffset = -1; table->IsSortSpecsDirty = true; table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934) table->InstanceInteracted = -1; @@ -3829,6 +3831,8 @@ static size_t TableSettingsCalcChunkSize(int columns_count) ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) { ImGuiContext& g = *GImGui; + //ImGuiTableSettings* old_settings = TableSettingsFindByID(id); // Comment out sanity check to avoid unnecessary lookups. + //IM_ASSERT(old_settings == NULL || old_settings->ColumnsCountMax < columns_count); ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count)); TableSettingsInit(settings, id, columns_count, columns_count); return settings; @@ -3848,14 +3852,12 @@ ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) // Get settings for a given table, NULL if none ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) { - if (table->SettingsOffset != -1) - { - ImGuiContext& g = *GImGui; - ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); - IM_ASSERT(settings->ID == table->ID); - return settings; - } - return NULL; + if (table->SettingsOffset == -1) + return NULL; + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + IM_ASSERT(settings->ID == table->ID); + return settings; } // Restore initial state of table (with or without saved settings) From 7930405c73ec943f63b2a4c8ec7b688e1f5311a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2026 18:04:24 +0200 Subject: [PATCH 066/254] Tables: rework _TrackTopologyChanges to function without enforcing saving order/visibility. (#9108) --- imgui_tables.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index bfa1dd26d..5a1d86267 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3898,8 +3898,6 @@ void ImGui::TableSaveSettings(ImGuiTable* table) bool save_ref_scale = false; settings->SaveFlags = ImGuiTableFlags_None; - if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) - settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; @@ -3925,6 +3923,8 @@ void ImGui::TableSaveSettings(ImGuiTable* table) settings->SaveFlags |= ImGuiTableFlags_Sortable; if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; + if (column->ID != 0) + settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges; } settings->SaveFlags &= table->Flags; settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; @@ -3978,12 +3978,11 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) { int column_n = column_settings->Index; + //if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) if ((table->Flags & ImGuiTableFlags_TrackTopologyChanges) && (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges))// && has_columns_id) if (column_n >= table->ColumnsCount || table->Columns[column_n].ID != column_settings->ID) { - // - Case 1: done during initial table creation: we don't have ID yet. Write back loaded IDs, will reconcile in TableUpdateLayout(). - // - Case 2 (this code): we have columns identifiers already. - column_n = -1; + column_n = -1; // FIXME-RECONCILE for (int other_n = 0; other_n < table->ColumnsCount && column_n == -1; other_n++) if (table->Columns[other_n].ID == column_settings->ID) column_n = other_n; @@ -4006,11 +4005,14 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) } if (settings->SaveFlags & ImGuiTableFlags_Reorderable) column->DisplayOrder = column_settings->DisplayOrder; + else + column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults() if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1) - column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1; + column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1); column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } + table->SettingsLoadedFlags |= ImGuiTableFlags_Reorderable; // We handle above in code above. } struct ImGuiTableFixDisplayOrderColumnData From b9cf21733db4db0bbf8990c169fde2dd12bae427 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2026 18:06:10 +0200 Subject: [PATCH 067/254] Tables: avoid forcing a settings load on count change. (#9108) At this very moment, I don't have a specific need/use for it. Went into this direction with solving the bigger problem, and it seems right but is not hard required right now. --- imgui_tables.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 5a1d86267..e449b7ea3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -578,7 +578,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->RawData == NULL) { TableBeginInitMemory(table, columns_count); - table->IsInitializing = table->IsSettingsRequestLoad = true; + table->IsInitializing = true; } if (table->IsResetAllRequest) TableResetSettings(table); @@ -586,7 +586,10 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG { // Initialize if (is_new_table) + { table->SettingsOffset = -1; + table->IsSettingsRequestLoad = true; + } table->IsSortSpecsDirty = true; table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934) table->InstanceInteracted = -1; From 570c0e03b78f5dd76812e1edd727fa3af9a5de5c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2026 23:13:09 +0200 Subject: [PATCH 068/254] Tables: extract part of TableLoadSettingsForColumns() into TableLoadSettingsForColumn(). (#9108) --- imgui_internal.h | 2 ++ imgui_tables.cpp | 51 ++++++++++++++++++++++++------------------------ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index ff66e2989..c3cea78cd 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3263,6 +3263,8 @@ namespace ImGui // Tables: Settings IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableLoadSettingsForColumns(ImGuiTable* table); + IMGUI_API void TableLoadSettingsForColumn(ImGuiTableColumn* column, const ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags); IMGUI_API void TableSaveSettings(ImGuiTable* table); IMGUI_API void TableResetSettings(ImGuiTable* table); IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e449b7ea3..75af671e1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3962,7 +3962,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; - // (TableUpdateLayout() will further read from settings data) + // TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data. } void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) @@ -3973,15 +3973,11 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) if (settings == NULL) return; - // When using TrackTopologyChanges mode, columns ID are either all sets, either all cleared (initially). - //const bool has_columns_id = table->Columns[0].ID != 0; - // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) { int column_n = column_settings->Index; - //if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) if ((table->Flags & ImGuiTableFlags_TrackTopologyChanges) && (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges))// && has_columns_id) if (column_n >= table->ColumnsCount || table->Columns[column_n].ID != column_settings->ID) { @@ -3993,31 +3989,34 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) if (column_n < 0 || column_n >= table->ColumnsCount) continue; - - ImGuiTableColumn* column = &table->Columns[column_n]; - column->IsLoadedSettings = true; - if (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges) - column->ID = column_settings->ID; - if (settings->SaveFlags & ImGuiTableFlags_Resizable) - { - if (column_settings->IsStretch) - column->StretchWeight = column_settings->WidthOrWeight; - else - column->WidthRequest = column_settings->WidthOrWeight; - column->AutoFitQueue = 0x00; - } - if (settings->SaveFlags & ImGuiTableFlags_Reorderable) - column->DisplayOrder = column_settings->DisplayOrder; - else - column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults() - if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1) - column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1); - column->SortOrder = column_settings->SortOrder; - column->SortDirection = column_settings->SortDirection; + TableLoadSettingsForColumn(&table->Columns[column_n], column_settings, settings->SaveFlags); } table->SettingsLoadedFlags |= ImGuiTableFlags_Reorderable; // We handle above in code above. } +void ImGui::TableLoadSettingsForColumn(ImGuiTableColumn* column, const ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags) +{ + column->IsLoadedSettings = true; + if (load_flags & ImGuiTableFlags_TrackTopologyChanges) + column->ID = column_settings->ID; + if (load_flags & ImGuiTableFlags_Resizable) + { + if (column_settings->IsStretch) + column->StretchWeight = column_settings->WidthOrWeight; + else + column->WidthRequest = column_settings->WidthOrWeight; + column->AutoFitQueue = 0x00; + } + if (load_flags & ImGuiTableFlags_Reorderable) + column->DisplayOrder = column_settings->DisplayOrder; + else + column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults() + if ((load_flags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1) + column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1); + column->SortOrder = column_settings->SortOrder; + column->SortDirection = column_settings->SortDirection; +} + struct ImGuiTableFixDisplayOrderColumnData { ImGuiTableColumnIdx Idx; From b522576054ea396f3456def5c1ac575af0a4a0c7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jun 2026 19:28:25 +0200 Subject: [PATCH 069/254] Tables: tracking topology changes by default. Match by ID then in sequential order for remaining. (#9108, #4046) Removed ImGuiTableFlags_TrackTopologyChanges. --- docs/CHANGELOG.txt | 13 ++++ imgui.h | 1 - imgui_internal.h | 16 ++-- imgui_tables.cpp | 187 +++++++++++++++++++++++++++++---------------- 4 files changed, 144 insertions(+), 73 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9b34d83f6..06dd0b1ab 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -59,6 +59,19 @@ Other Changes: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. - Tables: + - Redesigned/rewrote code to reconcile columns and settings on topology changes. (#9108) + - When a column label is passed to TableSetupColumn(), the underlying identifier + is used to match live columns data and .ini settings data when changing. + This makes it possible to add/remove columns from a table without losing + neither live data neither .ini settings data. + - PS: Note that this is distinct from toggling column visibility or reordering + columns, which was always possible. The new matching makes it easier to create + tables that are entirely customized by user or code, without losing state. + - Columns without identifiers or with duplicate identifiers are matched + sequentially, matching old behavior. + - Column ID are stored in .ini file. + - Code is being tested both for live topology changes and for loading .ini + data with mismatched topology. - Context Menu: added a "Reset" sub-menu with a "Reset Visibility" option. (which is greyed out when using default settings) - Headers: fixed label being clipped early to reserve space for a sort marker diff --git a/imgui.h b/imgui.h index e4a8d0e07..720e3c8cd 100644 --- a/imgui.h +++ b/imgui.h @@ -2077,7 +2077,6 @@ enum ImGuiTableFlags_ ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). // Miscellaneous ImGuiTableFlags_HighlightHoveredColumn = 1 << 28, // Highlight column headers when hovered (may evolve into a fuller highlight) - ImGuiTableFlags_TrackTopologyChanges = 1 << 29, // [BETA] Saved columns data keyed by their identifier. Allow data to persist after column addition/deletion/reordering. Requires valid identifier for all columns. // [Internal] Combinations and masks ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame, diff --git a/imgui_internal.h b/imgui_internal.h index c3cea78cd..ed5d9c9e7 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2934,9 +2934,10 @@ struct ImGuiTableColumn bool IsVisibleY; bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not. bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen). - bool IsPreserveWidthAuto; - bool IsJustCreated; - bool IsLoadedSettings; + bool IsPreserveWidthAuto : 1; + bool IsJustCreated : 1; + bool IsLoadedSettings : 1; + bool SrcFoundReconcileTarget : 1; ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte ImU8 AutoFitQueue : 4; // Queue of 4 values for the next 4 frames to request auto-fit ImU8 CannotSkipItemsQueue : 4; // Queue of 4 values for the next 4 frames to disable Clipped/SkipItem @@ -2971,8 +2972,8 @@ struct ImGuiTableReconcileColumnData ImGuiID UserData; // Reconcile data - ImGuiTableColumnIdx ColumnNewIdx; - ImGuiTableColumnIdx ColumnOldIdx; + ImGuiTableColumnIdx ColumnNewIdx; // Index in the current table. + ImGuiTableColumnIdx ColumnOldIdx; // Index in the previous frame table. ImGuiTableColumn ColumnOldData; // Full backup of the column. Could be avoided by storing 1 of them and applying reconcile in the right order. Not worth bothering. }; @@ -3109,6 +3110,7 @@ struct IMGUI_API ImGuiTable bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; + bool IsReconcileMode; bool IsSortSpecsDirty; bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). @@ -3177,6 +3179,7 @@ struct ImGuiTableColumnSettings ImU8 SortDirection : 2; ImS8 IsEnabled : 2; // "Visible" in .ini file ImU8 IsStretch : 1; + bool IsLoaded : 1; // Using during loading to mark finding a matching column. ImGuiTableColumnSettings() { @@ -3187,6 +3190,7 @@ struct ImGuiTableColumnSettings SortDirection = ImGuiSortDirection_None; IsEnabled = -1; IsStretch = 0; + IsLoaded = false; } }; @@ -3264,7 +3268,7 @@ namespace ImGui // Tables: Settings IMGUI_API void TableLoadSettings(ImGuiTable* table); IMGUI_API void TableLoadSettingsForColumns(ImGuiTable* table); - IMGUI_API void TableLoadSettingsForColumn(ImGuiTableColumn* column, const ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags); + IMGUI_API void TableLoadSettingsForColumn(ImGuiTableColumn* column, ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags); IMGUI_API void TableSaveSettings(ImGuiTable* table); IMGUI_API void TableResetSettings(ImGuiTable* table); IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 75af671e1..3eb79f6f7 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -592,6 +592,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } table->IsSortSpecsDirty = true; table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934) + table->IsReconcileMode = false; table->InstanceInteracted = -1; table->ContextPopupColumn = -1; table->ReorderColumn = table->ReorderColumnDstOrder = table->ResizedColumn = table->LastResizedColumn = -1; @@ -850,12 +851,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const int columns_count = table->ColumnsCount; // Reconcile moved columns - if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) - { - IM_ASSERT_USER_ERROR(table->DeclColumnsCount == columns_count, "When using TrackTopologyChanges: must call TableSetupColumn() with a valid identifier for all columns!"); - if (temp_data->ReconcileColumnsRequests.Size > 0) - TableReconcileColumns(table); - } + if (temp_data->ReconcileColumnsRequests.Size > 0) + TableReconcileColumns(table); if (temp_data->OldColumnsRawData) { IM_FREE(temp_data->OldColumnsRawData); @@ -1756,59 +1753,93 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo // When ID changed or a column moved: defer the request until layout where we will process full reconcile. const int column_idx = table->DeclColumnsCount++; - if (table->Flags & ImGuiTableFlags_TrackTopologyChanges) + ImGuiTableColumn* column = &table->Columns[column_idx]; + + // If topology change goes into reconcile mode + if (!table->IsReconcileMode && column->ID != column_id) { - // On column count change: search the previous columns: - // - the live Columns[] array is truncated on shrink, so a moved column may only exist in old data. - // - the live Columns[] array may already be partially reset by the TableInitColumnDefaults() call above. - // When count changed, even a never-updated slot (prev_column_id == 0 while initializing) may have old data to recover. - // Defer applying data, TableUpdateLayout() will call TableSetupColumnApply(). - IM_ASSERT_USER_ERROR(column_id != 0, "When using TrackTopologyChanges: Must call TableSetupColumn() with a valid identifier for all columns!"); - ImGuiTableColumn* column = &table->Columns[column_idx]; - const bool may_have_old_data = (column->ID != 0 || !table->IsInitializing || !table->TempData->OldColumnsData.empty()); - if (column->ID != column_id && column_id != 0 && may_have_old_data) - { - table->TempData->ReconcileColumnsRequests.push_back(ImGuiTableReconcileColumnData()); - ImGuiTableReconcileColumnData& reconcile_data = table->TempData->ReconcileColumnsRequests.back(); - reconcile_data.ID = column_id; - reconcile_data.NameOffset = name_offset; - reconcile_data.Flags = flags; - reconcile_data.InitWidthOrWeight = init_width_or_weight; - reconcile_data.UserData = user_data; - reconcile_data.ColumnNewIdx = (ImGuiTableColumnIdx)column_idx; - reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)-1; - ImSpan& search_columns = table->TempData->OldColumnsData.empty() ? table->Columns : table->TempData->OldColumnsData; - for (ImGuiTableColumn& old_column : search_columns) - if (old_column.ID == column_id) - { - reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)search_columns.index_from_ptr(&old_column); - reconcile_data.ColumnOldData = old_column; - break; - } - column->NameOffset = name_offset; // Allow TableGetColumnName() to work before layout. - return; - } + table->IsReconcileMode = true; + table->TempData->ReconcileColumnsRequests.reserve(table->ColumnsCount - column_idx); } - - TableSetupColumnApply(table, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data); + + // Fast/common path + if (table->IsReconcileMode == false) + { + TableSetupColumnApply(table, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data); + return; + } + + // Reconcile path: defer applying data to TableUpdateLayout() -> TableReconcileMovedColumns() -> TableSetupColumnApply(). + // On column count change: search the previous columns: + // - the live Columns[] array is truncated on shrink, so a moved column may only exist in old data. + // - the live Columns[] array may already be partially reset. + table->TempData->ReconcileColumnsRequests.push_back(ImGuiTableReconcileColumnData()); + ImGuiTableReconcileColumnData& reconcile_data = table->TempData->ReconcileColumnsRequests.back(); + reconcile_data.ID = column_id; + reconcile_data.NameOffset = name_offset; + reconcile_data.Flags = flags; + reconcile_data.InitWidthOrWeight = init_width_or_weight; + reconcile_data.UserData = user_data; + reconcile_data.ColumnNewIdx = (ImGuiTableColumnIdx)column_idx; + reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)-1; + + // Allow TableGetColumnName() to work before layout + column->NameOffset = name_offset; } +// NB: This was written to be similar to the logic in TableLoadSettingsForColumns(). void ImGui::TableReconcileColumns(ImGuiTable* table) { ImGuiContext& g = *GImGui; ImGuiTableTempData* temp_data = table->TempData; IM_UNUSED(g); - IMGUI_DEBUG_LOG_TABLE("[table] Reconcile moved columns for table 0x%08X\n", table->ID); - for (ImGuiTableReconcileColumnData& reconcile_data : temp_data->ReconcileColumnsRequests) + IMGUI_DEBUG_LOG_TABLE("[table] Reconcile columns for table 0x%08X\n", table->ID); + + ImSpan& src_columns = table->TempData->OldColumnsData.empty() ? table->Columns : table->TempData->OldColumnsData; + for (ImGuiTableColumn& src_column : src_columns) + src_column.SrcFoundReconcileTarget = false; + + // Find matches for named columns. + int matches = 0; + ImVector& reconcile_requests = temp_data->ReconcileColumnsRequests; + for (ImGuiTableReconcileColumnData& reconcile_data : reconcile_requests) + if (reconcile_data.ID != 0) + for (ImGuiTableColumn& src_column : src_columns) + if (src_column.ID == reconcile_data.ID && !src_column.SrcFoundReconcileTarget) + { + reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column); + reconcile_data.ColumnOldData = src_column; + src_column.SrcFoundReconcileTarget = true; + matches++; + break; + } + + // Remaining entries are matched sequentially. + int dst_idx = 0; // index in reconcile array + if (matches != reconcile_requests.Size) + for (ImGuiTableColumn& src_column : src_columns) + if (!src_column.SrcFoundReconcileTarget) + { + while (dst_idx < reconcile_requests.Size && reconcile_requests[dst_idx].ColumnOldIdx != (ImGuiTableColumnIdx)-1) + dst_idx++; + if (dst_idx == reconcile_requests.Size) + break; + reconcile_requests[dst_idx].ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column); + reconcile_requests[dst_idx].ColumnOldData = src_column; + IM_ASSERT(src_column.SrcFoundReconcileTarget == false); + } + + // Apply in the final pass. Because it is possible that src_columns == table->Columns we went through a temporary copy. + for (ImGuiTableReconcileColumnData& reconcile_data : reconcile_requests) { - ImGuiTableColumn* column = &table->Columns[reconcile_data.ColumnNewIdx]; - *column = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data. + table->Columns[reconcile_data.ColumnNewIdx] = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data. TableSetupColumnApply(table, reconcile_data.ColumnNewIdx, reconcile_data.ID, reconcile_data.NameOffset, reconcile_data.Flags, reconcile_data.InitWidthOrWeight, reconcile_data.UserData); IMGUI_DEBUG_LOG_TABLE("[table] - old %d -> new %d \"%s\"\n", reconcile_data.ColumnOldIdx, reconcile_data.ColumnNewIdx, TableGetColumnName(table, reconcile_data.ColumnNewIdx)); // Log at the end so NameOffset was copied. } TableFixDisplayOrder(table); table->IsSettingsDirty = true; // FIXME-RECONCILE: Necessary? - temp_data->ReconcileColumnsRequests.resize(0); // GC-ed once in NewFrame() + table->IsReconcileMode = false; + reconcile_requests.resize(0); // GC-ed once in NewFrame() } // [Public] @@ -3926,8 +3957,6 @@ void ImGui::TableSaveSettings(ImGuiTable* table) settings->SaveFlags |= ImGuiTableFlags_Sortable; if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; - if (column->ID != 0) - settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges; } settings->SaveFlags &= table->Flags; settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; @@ -3965,6 +3994,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) // TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data. } +// NB: This was written to be similar to the logic in TableReconcileColumns(). void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) { for (ImGuiTableColumn& column : table->Columns) @@ -3974,31 +4004,57 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) return; // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn + int matches = 0; ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); - for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) + for (int column_settings_n = 0; column_settings_n < settings->ColumnsCount; column_settings_n++, column_settings++) { - int column_n = column_settings->Index; - if ((table->Flags & ImGuiTableFlags_TrackTopologyChanges) && (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges))// && has_columns_id) - if (column_n >= table->ColumnsCount || table->Columns[column_n].ID != column_settings->ID) - { - column_n = -1; // FIXME-RECONCILE - for (int other_n = 0; other_n < table->ColumnsCount && column_n == -1; other_n++) - if (table->Columns[other_n].ID == column_settings->ID) - column_n = other_n; - } - - if (column_n < 0 || column_n >= table->ColumnsCount) - continue; - TableLoadSettingsForColumn(&table->Columns[column_n], column_settings, settings->SaveFlags); + const int src_idx = column_settings->Index; + int dst_idx = src_idx; + column_settings->IsLoaded = false; + if (dst_idx >= 0 && dst_idx < table->ColumnsCount && table->Columns[dst_idx].ID == column_settings->ID) + { + // Fast/Common path + TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); + matches++; + } + else if (column_settings->ID != 0) + { + // Find match for named columns. + for (int other_n = 0; other_n < table->ColumnsCount; other_n++) + if (table->Columns[other_n].ID == column_settings->ID) + { + dst_idx = other_n; + TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); + matches++; + break; + } + } } + + // Remaining entries are matched sequentially + if (matches != settings->ColumnsCount) + { + int dst_idx = 0; + column_settings = settings->GetColumnSettings(); + for (int column_settings_n = 0; column_settings_n < settings->ColumnsCount; column_settings_n++, column_settings++) + if (!column_settings->IsLoaded) + { + while (dst_idx < table->ColumnsCount && table->Columns[dst_idx].IsLoadedSettings) + dst_idx++; + if (dst_idx >= table->ColumnsCount) + break; + TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); + dst_idx++; + } + } + table->SettingsLoadedFlags |= ImGuiTableFlags_Reorderable; // We handle above in code above. } -void ImGui::TableLoadSettingsForColumn(ImGuiTableColumn* column, const ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags) +void ImGui::TableLoadSettingsForColumn(ImGuiTableColumn* column, ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags) { column->IsLoadedSettings = true; - if (load_flags & ImGuiTableFlags_TrackTopologyChanges) - column->ID = column_settings->ID; + column_settings->IsLoaded = true; if (load_flags & ImGuiTableFlags_Resizable) { if (column_settings->IsStretch) @@ -4115,7 +4171,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } - if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges; } + if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; } } } @@ -4133,7 +4189,6 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; - const bool save_id = (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges) != 0; // We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings". buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve @@ -4153,7 +4208,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle if (save_visible) { buf->appendf(" Visible=%d", column->IsEnabled); } if (save_order) { buf->appendf(" Order=%d", column->DisplayOrder); } if (save_sort && column->SortOrder != -1) { buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); } - if (save_id && column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } + if (column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } buf->append("\n"); } buf->append("\n"); From ec69495ca8b49806b4be8dc464e67f1ebd119d30 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 23 Jun 2026 16:51:38 +0200 Subject: [PATCH 070/254] Tables: fixed some cases of reconcile/load handling when using duplicate identifiers. (#9108) --- imgui_internal.h | 3 ++- imgui_tables.cpp | 68 +++++++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index ed5d9c9e7..0bec47037 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2937,7 +2937,8 @@ struct ImGuiTableColumn bool IsPreserveWidthAuto : 1; bool IsJustCreated : 1; bool IsLoadedSettings : 1; - bool SrcFoundReconcileTarget : 1; + bool IsNeedReconcileSrc : 1; + bool IsNeedReconcileDst : 1; ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte ImU8 AutoFitQueue : 4; // Queue of 4 values for the next 4 frames to request auto-fit ImU8 CannotSkipItemsQueue : 4; // Queue of 4 values for the next 4 frames to disable Clipped/SkipItem diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3eb79f6f7..715ea6816 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -573,6 +573,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG IM_ASSERT(temp_data->OldColumnsRawData == NULL); temp_data->OldColumnsRawData = table->RawData; // Freed during layout temp_data->OldColumnsData = table->Columns; + for (ImGuiTableColumn& src_column : table->TempData->OldColumnsData) + src_column.IsNeedReconcileSrc = true; table->RawData = NULL; } if (table->RawData == NULL) @@ -1756,7 +1758,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo ImGuiTableColumn* column = &table->Columns[column_idx]; // If topology change goes into reconcile mode - if (!table->IsReconcileMode && column->ID != column_id) + if (table->IsReconcileMode == false && column->ID != column_id) { table->IsReconcileMode = true; table->TempData->ReconcileColumnsRequests.reserve(table->ColumnsCount - column_idx); @@ -1766,6 +1768,9 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo if (table->IsReconcileMode == false) { TableSetupColumnApply(table, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data); + column->IsNeedReconcileSrc = column->IsNeedReconcileDst = false; + if (column_idx < table->TempData->OldColumnsData.size()) + table->TempData->OldColumnsData[column_idx].IsNeedReconcileSrc = false; return; } @@ -1782,9 +1787,8 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo reconcile_data.UserData = user_data; reconcile_data.ColumnNewIdx = (ImGuiTableColumnIdx)column_idx; reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)-1; - - // Allow TableGetColumnName() to work before layout - column->NameOffset = name_offset; + column->NameOffset = name_offset; // Allow TableGetColumnName() to work before layout + column->IsNeedReconcileSrc = column->IsNeedReconcileDst = true; } // NB: This was written to be similar to the logic in TableLoadSettingsForColumns(). @@ -1795,9 +1799,8 @@ void ImGui::TableReconcileColumns(ImGuiTable* table) IM_UNUSED(g); IMGUI_DEBUG_LOG_TABLE("[table] Reconcile columns for table 0x%08X\n", table->ID); + ImSpan& dst_columns = table->Columns; ImSpan& src_columns = table->TempData->OldColumnsData.empty() ? table->Columns : table->TempData->OldColumnsData; - for (ImGuiTableColumn& src_column : src_columns) - src_column.SrcFoundReconcileTarget = false; // Find matches for named columns. int matches = 0; @@ -1805,11 +1808,13 @@ void ImGui::TableReconcileColumns(ImGuiTable* table) for (ImGuiTableReconcileColumnData& reconcile_data : reconcile_requests) if (reconcile_data.ID != 0) for (ImGuiTableColumn& src_column : src_columns) - if (src_column.ID == reconcile_data.ID && !src_column.SrcFoundReconcileTarget) + if (src_column.ID == reconcile_data.ID && src_column.IsNeedReconcileSrc) { + ImGuiTableColumn& dst_column = dst_columns[reconcile_data.ColumnNewIdx]; + IM_ASSERT(src_column.IsNeedReconcileSrc && dst_column.IsNeedReconcileDst); + src_column.IsNeedReconcileSrc = dst_column.IsNeedReconcileDst = false; reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column); reconcile_data.ColumnOldData = src_column; - src_column.SrcFoundReconcileTarget = true; matches++; break; } @@ -1818,21 +1823,25 @@ void ImGui::TableReconcileColumns(ImGuiTable* table) int dst_idx = 0; // index in reconcile array if (matches != reconcile_requests.Size) for (ImGuiTableColumn& src_column : src_columns) - if (!src_column.SrcFoundReconcileTarget) - { - while (dst_idx < reconcile_requests.Size && reconcile_requests[dst_idx].ColumnOldIdx != (ImGuiTableColumnIdx)-1) - dst_idx++; - if (dst_idx == reconcile_requests.Size) - break; - reconcile_requests[dst_idx].ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column); - reconcile_requests[dst_idx].ColumnOldData = src_column; - IM_ASSERT(src_column.SrcFoundReconcileTarget == false); - } + { + if (!src_column.IsNeedReconcileSrc) + continue; + while (dst_idx < reconcile_requests.Size && reconcile_requests[dst_idx].ColumnOldIdx != (ImGuiTableColumnIdx)-1) + dst_idx++; + if (dst_idx == reconcile_requests.Size) + break; + ImGuiTableReconcileColumnData& reconcile_data = reconcile_requests[dst_idx]; + ImGuiTableColumn& dst_column = dst_columns[reconcile_data.ColumnNewIdx]; + IM_ASSERT(src_column.IsNeedReconcileSrc && dst_column.IsNeedReconcileDst); + dst_column.IsNeedReconcileSrc = dst_column.IsNeedReconcileDst = false; + reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column); + reconcile_data.ColumnOldData = src_column; + } // Apply in the final pass. Because it is possible that src_columns == table->Columns we went through a temporary copy. for (ImGuiTableReconcileColumnData& reconcile_data : reconcile_requests) { - table->Columns[reconcile_data.ColumnNewIdx] = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data. + table->Columns[reconcile_data.ColumnNewIdx] = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data (which will set IsJustCreated=true) TableSetupColumnApply(table, reconcile_data.ColumnNewIdx, reconcile_data.ID, reconcile_data.NameOffset, reconcile_data.Flags, reconcile_data.InitWidthOrWeight, reconcile_data.UserData); IMGUI_DEBUG_LOG_TABLE("[table] - old %d -> new %d \"%s\"\n", reconcile_data.ColumnOldIdx, reconcile_data.ColumnNewIdx, TableGetColumnName(table, reconcile_data.ColumnNewIdx)); // Log at the end so NameOffset was copied. } @@ -3831,6 +3840,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags // - TableResetSettings() // - TableSaveSettings() [Internal] // - TableLoadSettings() [Internal] +// - TableLoadSettingsForColumns() [Internal] // - TableSettingsHandler_ClearAll() [Internal] // - TableSettingsHandler_ApplyAll() [Internal] // - TableSettingsHandler_ReadOpen() [Internal] @@ -4008,27 +4018,25 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); for (int column_settings_n = 0; column_settings_n < settings->ColumnsCount; column_settings_n++, column_settings++) { - const int src_idx = column_settings->Index; - int dst_idx = src_idx; column_settings->IsLoaded = false; - if (dst_idx >= 0 && dst_idx < table->ColumnsCount && table->Columns[dst_idx].ID == column_settings->ID) + const int dst_idx = column_settings->Index; + if (dst_idx >= 0 && dst_idx < table->ColumnsCount && table->Columns[dst_idx].ID == column_settings->ID && !table->Columns[dst_idx].IsLoadedSettings) { // Fast/Common path TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); matches++; + continue; } - else if (column_settings->ID != 0) - { - // Find match for named columns. - for (int other_n = 0; other_n < table->ColumnsCount; other_n++) - if (table->Columns[other_n].ID == column_settings->ID) + + // Find match for named columns. + if (column_settings->ID != 0) + for (ImGuiTableColumn& column : table->Columns) + if (column.ID == column_settings->ID && !column.IsLoadedSettings) { - dst_idx = other_n; - TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); + TableLoadSettingsForColumn(&column, column_settings, settings->SaveFlags); matches++; break; } - } } // Remaining entries are matched sequentially From e74453760b4668aaffd6f0e91c35df2b9b766e12 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 24 Jun 2026 15:25:32 +0200 Subject: [PATCH 071/254] Tables: rewrote TableLoadSettingsForColumns() to match TableReconcileColumn() logic more precisely. (#9108) Fixes issues with e.g. duplicate identifiers. See "table_topology_change_1" test. --- imgui_tables.cpp | 71 ++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 715ea6816..128cd0136 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4013,50 +4013,49 @@ void ImGui::TableLoadSettingsForColumns(ImGuiTable* table) if (settings == NULL) return; - // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn - int matches = 0; - ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); - for (int column_settings_n = 0; column_settings_n < settings->ColumnsCount; column_settings_n++, column_settings++) - { - column_settings->IsLoaded = false; - const int dst_idx = column_settings->Index; - if (dst_idx >= 0 && dst_idx < table->ColumnsCount && table->Columns[dst_idx].ID == column_settings->ID && !table->Columns[dst_idx].IsLoadedSettings) - { - // Fast/Common path - TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); - matches++; - continue; - } + table->SettingsLoadedFlags |= ImGuiTableFlags_Reorderable; // We handle above in code above. - // Find match for named columns. - if (column_settings->ID != 0) - for (ImGuiTableColumn& column : table->Columns) - if (column.ID == column_settings->ID && !column.IsLoadedSettings) + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + + // Fast path + int matches = 0; + for (int n = 0; n < table->ColumnsCount; n++) + { + if (n >= settings->ColumnsCount || column_settings[n].ID != table->Columns[n].ID) + break; + TableLoadSettingsForColumn(&table->Columns[n], &column_settings[n], settings->SaveFlags); + matches++; + } + if (matches == settings->ColumnsCount) + return; + const int settings_start_n = matches; // Small optimization + for (int n = settings_start_n; n < settings->ColumnsCount; n++) + column_settings[n].IsLoaded = false; + + // Find matches for named columns + for (ImGuiTableColumn& column : table->Columns) + if (column.ID != 0 && !column.IsLoadedSettings) + for (int n = settings_start_n; n < settings->ColumnsCount; n++) + if (column_settings[n].ID == column.ID && !column_settings[n].IsLoaded) { - TableLoadSettingsForColumn(&column, column_settings, settings->SaveFlags); + TableLoadSettingsForColumn(&column, &column_settings[n], settings->SaveFlags); matches++; break; } - } // Remaining entries are matched sequentially - if (matches != settings->ColumnsCount) - { - int dst_idx = 0; - column_settings = settings->GetColumnSettings(); - for (int column_settings_n = 0; column_settings_n < settings->ColumnsCount; column_settings_n++, column_settings++) - if (!column_settings->IsLoaded) - { - while (dst_idx < table->ColumnsCount && table->Columns[dst_idx].IsLoadedSettings) - dst_idx++; - if (dst_idx >= table->ColumnsCount) - break; - TableLoadSettingsForColumn(&table->Columns[dst_idx], column_settings, settings->SaveFlags); + int dst_idx = 0; + for (int n = settings_start_n; n < settings->ColumnsCount; n++) + if (!column_settings[n].IsLoaded) + { + while (dst_idx < table->ColumnsCount && table->Columns[dst_idx].IsLoadedSettings) dst_idx++; - } - } - - table->SettingsLoadedFlags |= ImGuiTableFlags_Reorderable; // We handle above in code above. + if (dst_idx >= table->ColumnsCount) + break; + TableLoadSettingsForColumn(&table->Columns[dst_idx], &column_settings[n], settings->SaveFlags); + dst_idx++; + } } void ImGui::TableLoadSettingsForColumn(ImGuiTableColumn* column, ImGuiTableColumnSettings* column_settings, ImGuiTableFlags load_flags) From be374fa598aae658ea43f843a6d821b2957d4a83 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 24 Jun 2026 17:32:36 +0200 Subject: [PATCH 072/254] TreeNode: fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 06dd0b1ab..a160931c3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -90,6 +90,8 @@ Other Changes: - Nav: - Fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] +- TreeNode: + - Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7b8c4e9ce..3fb634be5 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7116,9 +7116,9 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (hovered || selected) { const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false, 0.0f); } - RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + RenderNavCursor(frame_bb, id, nav_render_cursor_flags | ImGuiNavRenderCursorFlags_NoRounding); if (span_all_columns && !span_all_columns_label) TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) From 26432cfdc6c144b0ce5cf82a04afd6c359059517 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 24 Jun 2026 17:56:54 +0200 Subject: [PATCH 073/254] Style, Nav: scale the NavCursor border thickness when using large values with `ScallAllSizes()`. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a160931c3..ab70c87fd 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -92,6 +92,8 @@ Other Changes: instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] - TreeNode: - Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) +- Style: + - Scale the NavCursor border thickness when using large values with `ScallAllSizes()`. - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imgui.cpp b/imgui.cpp index c45606863..93d188782 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4045,14 +4045,15 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; ImRect display_rect = bb; display_rect.ClipWith(window->ClipRect); - const float thickness = 2.0f; + const float scale_factor = GetScale(); // FIXME-DPI + const float thickness = (float)(int)ImMax(2.0f, 1.5f * scale_factor); if (flags & ImGuiNavRenderCursorFlags_Compact) { window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, thickness); } else { - const float distance = 3.0f + thickness * 0.5f; + const float distance = (float)(int)(3.0f + thickness * 0.5f); display_rect.Expand(ImVec2(distance, distance)); bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) From 0e355af09e8dd949c12f9069090c9811d6b3fb2e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 24 Jun 2026 18:33:43 +0200 Subject: [PATCH 074/254] RenderNavCursor: take rounding as input. Removed ImGuiNavRenderCursorFlags_NoRounding. Toward #7589 --- imgui.cpp | 10 ++++++++-- imgui_internal.h | 4 ++-- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 6 +++--- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 93d188782..c9b23bc4f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4026,7 +4026,7 @@ void ImGui::RenderColorComponentMarker(const ImRect& bb, ImU32 col, float roundi RenderRectFilledInRangeH(window->DrawList, bb, col, bb.Min.x, ImMin(bb.Min.x + g.Style.ColorMarkerSize, bb.Max.x), rounding); } -void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags) +void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags, float rounding) { ImGuiContext& g = *GImGui; if (id != g.NavId) @@ -4042,7 +4042,13 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl if (window->DC.NavHideHighlightOneFrame) return; - float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (rounding < 0.0f && (flags & ImGuiNavRenderCursorFlags_NoRounding)) + rounding = 0.0f; +#endif + if (rounding < 0.0f) + rounding = g.Style.FrameRounding; + ImRect display_rect = bb; display_rect.ClipWith(window->ClipRect); const float scale_factor = GetScale(); // FIXME-DPI diff --git a/imgui_internal.h b/imgui_internal.h index 0bec47037..df7ea651e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1728,8 +1728,8 @@ enum ImGuiNavRenderCursorFlags_ ImGuiNavRenderCursorFlags_None = 0, ImGuiNavRenderCursorFlags_Compact = 1 << 1, // Compact highlight, no padding/distance from focused item ImGuiNavRenderCursorFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) even when g.NavCursorVisible == false, aka even when using the mouse. - ImGuiNavRenderCursorFlags_NoRounding = 1 << 3, #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiNavRenderCursorFlags_NoRounding = 1 << 3, ImGuiNavHighlightFlags_None = ImGuiNavRenderCursorFlags_None, // Renamed in 1.91.4 ImGuiNavHighlightFlags_Compact = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.91.4 ImGuiNavHighlightFlags_AlwaysDraw = ImGuiNavRenderCursorFlags_AlwaysDraw, // Renamed in 1.91.4 @@ -3679,7 +3679,7 @@ namespace ImGui IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorComponentMarker(const ImRect& bb, ImU32 col, float rounding); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); - IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None); // Navigation highlight + IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None, float rounding = -1.0f); // Navigation highlight #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None) { RenderNavCursor(bb, id, flags); } // Renamed in 1.91.4 #endif diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 128cd0136..d59658942 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3395,7 +3395,7 @@ void ImGui::TableHeader(const char* label) if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); } - RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact, 0.0f); if (held) table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3fb634be5..045e8096d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7118,7 +7118,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false, 0.0f); } - RenderNavCursor(frame_bb, id, nav_render_cursor_flags | ImGuiNavRenderCursorFlags_NoRounding); + RenderNavCursor(frame_bb, id, nav_render_cursor_flags, 0.0f); if (span_all_columns && !span_all_columns_label) TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) @@ -7502,10 +7502,10 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } if (g.NavId == id) { - ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding; + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact; if (is_multi_select) nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle - RenderNavCursor(bb, id, nav_render_cursor_flags); + RenderNavCursor(bb, id, nav_render_cursor_flags, 0.0f); } } From 1c1241136adaf6ba1d6903bed9e8b4bd8ce9ae6a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 11:22:09 +0200 Subject: [PATCH 075/254] Tables: fixed (harmless) flag clearing issue in TableReconcileColumns(). (#9108) Was actually harmless because IsNeedReconcileSrc never read again for this column. --- imgui_tables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d59658942..b71304f25 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1833,7 +1833,7 @@ void ImGui::TableReconcileColumns(ImGuiTable* table) ImGuiTableReconcileColumnData& reconcile_data = reconcile_requests[dst_idx]; ImGuiTableColumn& dst_column = dst_columns[reconcile_data.ColumnNewIdx]; IM_ASSERT(src_column.IsNeedReconcileSrc && dst_column.IsNeedReconcileDst); - dst_column.IsNeedReconcileSrc = dst_column.IsNeedReconcileDst = false; + src_column.IsNeedReconcileSrc = dst_column.IsNeedReconcileDst = false; reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)src_columns.index_from_ptr(&src_column); reconcile_data.ColumnOldData = src_column; } From ac6f9683b5419381bc7f4f02262464c095ee6ece Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 11:41:24 +0200 Subject: [PATCH 076/254] Style: added MenuItemRounding, SelectableRounding, ImGuiStyleVar_MenuItemRounding, ImGuiStyleVar_SelectableRounding. (#7589, #9375, #9453) --- docs/CHANGELOG.txt | 4 ++++ imgui.cpp | 6 ++++++ imgui.h | 4 ++++ imgui_demo.cpp | 2 ++ imgui_widgets.cpp | 12 +++++++++--- 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ab70c87fd..736a2b67c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -93,6 +93,10 @@ Other Changes: - TreeNode: - Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) - Style: + - Added style.MenuItemRounding, ImGuiStyleVar_MenuItemRounding. (#7589, #9375, #9453) + - Added style.SelectableRounding, ImGuiStyleVar_SelectableRounding. (#7589, #9375, #9453) + The use of this is discouraged because it can easily create problems rendering e.g. + contiguous selection. - Scale the NavCursor border thickness when using large values with `ScallAllSizes()`. - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) diff --git a/imgui.cpp b/imgui.cpp index c9b23bc4f..f6fb61dcd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1533,6 +1533,8 @@ ImGuiStyle::ImGuiStyle() TreeLinesFlags = ImGuiTreeNodeFlags_DrawLinesNone; TreeLinesSize = 1.0f; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. TreeLinesRounding = 0.0f; // Radius of lines connecting child nodes to the vertical line. + MenuItemRounding = 0.0f; // Radius of MenuItem, BeginMenu rounding. + SelectableRounding = 0.0f; // Radius of selectable rounding. MODIFYING THIS IS DISCOURAGED. CONTIGUOUS SELECTIONS WILL NOT LOOK RIGHT. (#7589) DragDropTargetRounding = 0.0f; // Radius of the drag and drop target frame. DragDropTargetBorderSize = 2.0f; // Thickness of the drag and drop target border. DragDropTargetPadding = 3.0f; // Size to expand the drag and drop target from actual target item size. @@ -1612,6 +1614,8 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor); TreeLinesSize = ImTrunc(TreeLinesSize * scale_factor); TreeLinesRounding = ImTrunc(TreeLinesRounding * scale_factor); + MenuItemRounding = ImTrunc(MenuItemRounding * scale_factor); + SelectableRounding = ImTrunc(SelectableRounding * scale_factor); DragDropTargetRounding = ImTrunc(DragDropTargetRounding * scale_factor); DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor); DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor); @@ -3685,6 +3689,8 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesSize)}, // ImGuiStyleVar_TreeLinesSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesRounding)}, // ImGuiStyleVar_TreeLinesRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, MenuItemRounding)}, // ImGuiStyleVar_MenuItemRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableRounding)}, // ImGuiStyleVar_SelectableRounding { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DragDropTargetRounding)}, // ImGuiStyleVar_DragDropTargetRounding { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign diff --git a/imgui.h b/imgui.h index 720e3c8cd..14c695b9f 100644 --- a/imgui.h +++ b/imgui.h @@ -1856,6 +1856,8 @@ enum ImGuiStyleVar_ ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign ImGuiStyleVar_TreeLinesSize, // float TreeLinesSize ImGuiStyleVar_TreeLinesRounding, // float TreeLinesRounding + ImGuiStyleVar_MenuItemRounding, // float MenuItemRounding + ImGuiStyleVar_SelectableRounding, // float SelectableRounding ImGuiStyleVar_DragDropTargetRounding, // float DragDropTargetRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign @@ -2333,6 +2335,8 @@ struct ImGuiStyle ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. + float MenuItemRounding; // Radius of MenuItem, BeginMenu rounding. + float SelectableRounding; // Radius of Selectable rounding. MODIFYING THIS IS DISCOURAGED. CONTIGUOUS SELECTIONS WILL NOT LOOK RIGHT. (#7589) float DragDropTargetRounding; // Radius of the drag and drop target frame. When <0.0f: use FrameRounding. float DragDropTargetBorderSize; // Thickness of the drag and drop target border. float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e0bbd9af6..1bd300666 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8556,6 +8556,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("MenuItemRounding", &style.MenuItemRounding, 0.0f, 12.0f, "%.0f"); + // NB: SelectableRounding is intentionally NOT made visible here. We don't want to encourage people using that. SeparatorText("Scrollbar"); SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 045e8096d..cb9e97441 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7498,14 +7498,14 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106) ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + RenderFrame(bb.Min, bb.Max, col, false, style.SelectableRounding); } if (g.NavId == id) { ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact; if (is_multi_select) nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle - RenderNavCursor(bb, id, nav_render_cursor_flags, 0.0f); + RenderNavCursor(bb, id, nav_render_cursor_flags, style.SelectableRounding); } } @@ -9350,7 +9350,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) return false; ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; + ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None); @@ -9395,6 +9395,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) bool pressed; + const float backup_rounding = style.SelectableRounding; + style.SelectableRounding = style.MenuItemRounding; const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoAutoClosePopups | (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnClick; ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) @@ -9430,6 +9432,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); popup_pos = ImVec2(pos.x, text_pos.y - style.WindowPadding.y); } + style.SelectableRounding = backup_rounding; if (!enabled) EndDisabled(); @@ -9616,6 +9619,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut BeginDisabled(); // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. + const float backup_rounding = style.SelectableRounding; + style.SelectableRounding = style.MenuItemRounding; const ImGuiSelectableFlags selectable_flags = (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnRelease | (ImGuiSelectableFlags)ImGuiSelectableFlags_SetNavIdOnHover; ImGuiMenuColumns* offsets = &window->DC.MenuColumns; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) @@ -9659,6 +9664,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); } } + style.SelectableRounding = backup_rounding; // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394) // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here. From 60fbdbb8d8e6e720732ac0c819193f8aa4bf7861 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 14:03:35 +0200 Subject: [PATCH 077/254] Multi-Select: reworked ImGuiMultiSelectFlags_NoAutoSelect as it carried side-effects that were hardcoded/designed to use multi-selection on checkboxes. (#9391) --- docs/CHANGELOG.txt | 8 ++++++++ imgui.h | 1 + imgui_internal.h | 2 +- imgui_widgets.cpp | 10 +++++----- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 736a2b67c..c90713b14 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -77,6 +77,14 @@ Other Changes: - Headers: fixed label being clipped early to reserve space for a sort marker even when no sort marker is displayed. Auto-fitting a column still accounts for the possible marker, so that sorting after an auto-fit doesn't clip the label. +- Multi-Select + - Reworked ImGuiMultiSelectFlags_NoAutoSelect as it carried side-effects that + were hardcoded/designed to use multi-selection on checkboxes. (#9391) + Specifically removed those undocumented behaviors from _NoAutoSelect: + - Clicking a selected/checked item always unselect/uncheck. + - Shift+Click inverts targets value and copy to range. + - Shift+Keyboard copy selection source value to range. + Those behaviors are still happening on checkboxes used within multi-selection. - Fonts: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) diff --git a/imgui.h b/imgui.h index 14c695b9f..d498355f4 100644 --- a/imgui.h +++ b/imgui.h @@ -3056,6 +3056,7 @@ enum ImGuiMultiSelectFlags_ 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_SelectOnClickAlways | ImGuiMultiSelectFlags_SelectOnClickRelease, + ImGuiMultiSelectFlags_CheckboxMode_ = 1 << 20, // [Internal] // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_internal.h b/imgui_internal.h index df7ea651e..36b205d3c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3637,7 +3637,7 @@ namespace ImGui // Multi-Select API IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags); - IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); + IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed, ImGuiMultiSelectFlags extra_flags = 0); IMGUI_API void MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected); IMGUI_API void MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item); inline ImGuiBoxSelectState* GetBoxSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index cb9e97441..c8458eba1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1274,7 +1274,7 @@ bool ImGui::Checkbox(const char* label, bool* v) // Range-Selection/Multi-selection support (footer) if (is_multi_select) - MultiSelectItemFooter(id, &checked, &pressed); + MultiSelectItemFooter(id, &checked, &pressed, ImGuiMultiSelectFlags_CheckboxMode_); else if (pressed) checked = !checked; @@ -8307,7 +8307,7 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags // - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. // - Record current selection state for RangeSrc // This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. -void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) +void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed, ImGuiMultiSelectFlags extra_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -8327,7 +8327,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; - ImGuiMultiSelectFlags flags = ms->Flags; + ImGuiMultiSelectFlags flags = ms->Flags | extra_flags; const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0; bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0; bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0; @@ -8481,7 +8481,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue); if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) storage->RangeSrcItem = item_data; - if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + if ((flags & ImGuiMultiSelectFlags_CheckboxMode_) == 0) { // Shift+Arrow always select // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected) @@ -8501,7 +8501,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) else { // Ctrl inverts selection, otherwise always select - if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + if ((flags & ImGuiMultiSelectFlags_CheckboxMode_) == 0) selected = is_ctrl ? !selected : true; else selected = !selected; From 76f5be2749ed17f02f0fcef402894340baece0ac Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 22:10:52 +0200 Subject: [PATCH 078/254] Settings: minor struct packing. --- imgui.h | 2 +- imgui_internal.h | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/imgui.h b/imgui.h index d498355f4..4f659c38f 100644 --- a/imgui.h +++ b/imgui.h @@ -4035,7 +4035,7 @@ struct ImGuiPlatformIO // Optional: Platform locale // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point - ImWchar Platform_LocaleDecimalPoint; // '.' + ImWchar Platform_LocaleDecimalPoint; // '.' //------------------------------------------------------------------ // Input - Interface with Renderer Backend diff --git a/imgui_internal.h b/imgui_internal.h index 36b205d3c..7f07a5439 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2014,13 +2014,13 @@ struct ImGuiViewportP : public ImGuiViewport // (this is designed to be stored in a ImChunkStream buffer, with the variable-length Name following our structure) struct ImGuiWindowSettings { - ImGuiID ID; - ImVec2ih Pos; - ImVec2ih Size; - bool Collapsed; - bool IsChild; - bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) - bool WantDelete; // Set to invalidate/delete the settings entry + ImGuiID ID; + ImVec2ih Pos; + ImVec2ih Size; + bool Collapsed : 1; + bool IsChild : 1; + bool WantApply : 1; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) + bool WantDelete : 1; // Set to invalidate/delete the settings entry ImGuiWindowSettings() { memset((void*)this, 0, sizeof(*this)); } char* GetName() { return (char*)(this + 1); } @@ -3203,7 +3203,7 @@ struct ImGuiTableSettings float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. ImGuiTableColumnIdx ColumnsCount; ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher - bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) + bool WantApply : 1; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) ImGuiTableSettings() { memset((void*)this, 0, sizeof(*this)); } ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } From c66d9a8190eb5bd0e1cfeff9ebdb0b01747c9175 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Jun 2026 11:49:06 +0200 Subject: [PATCH 079/254] Examples, SDL2/SDL3: use SDL_GetWindowSizeInPixels() to create framebuffers. (#8761, #9124) + cc #7433 --- docs/CHANGELOG.txt | 4 +++- examples/example_sdl2_vulkan/main.cpp | 4 ++-- examples/example_sdl3_vulkan/main.cpp | 4 ++-- examples/example_sdl3_wgpu/main.cpp | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c90713b14..12c359693 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -127,7 +127,9 @@ Other Changes: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends to spawn multiple-thread to manage OpenGL. (#9403) - +- Examples: + - SDL2/SDL3: use `SDL_GetWindowSizeInPixels()` to create frame-buffers. Fixes issues + with non-fractional framebuffer size on Wayland. (#8761, #9124) [@billtran1632001] ----------------------------------------------------------------------- VERSION 1.92.8 (Released 2026-05-12) diff --git a/examples/example_sdl2_vulkan/main.cpp b/examples/example_sdl2_vulkan/main.cpp index 131712927..45a5a6de5 100644 --- a/examples/example_sdl2_vulkan/main.cpp +++ b/examples/example_sdl2_vulkan/main.cpp @@ -386,7 +386,7 @@ int main(int, char**) // Create Framebuffers int w, h; - SDL_GetWindowSize(window, &w, &h); + SDL_GetWindowSizeInPixels(window, &w, &h); ImGui_ImplVulkanH_Window* wd = &g_MainWindowData; SetupVulkanWindow(wd, surface, w, h); @@ -475,7 +475,7 @@ int main(int, char**) // Resize swap chain? int fb_width, fb_height; - SDL_GetWindowSize(window, &fb_width, &fb_height); + SDL_GetWindowSizeInPixels(window, &fb_width, &fb_height); if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) { ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); diff --git a/examples/example_sdl3_vulkan/main.cpp b/examples/example_sdl3_vulkan/main.cpp index 04d17ba70..0073b37c5 100644 --- a/examples/example_sdl3_vulkan/main.cpp +++ b/examples/example_sdl3_vulkan/main.cpp @@ -383,7 +383,7 @@ int main(int, char**) // Create Framebuffers int w, h; - SDL_GetWindowSize(window, &w, &h); + SDL_GetWindowSizeInPixels(window, &w, &h); ImGui_ImplVulkanH_Window* wd = &g_MainWindowData; SetupVulkanWindow(wd, surface, w, h); SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); @@ -477,7 +477,7 @@ int main(int, char**) // Resize swap chain? int fb_width, fb_height; - SDL_GetWindowSize(window, &fb_width, &fb_height); + SDL_GetWindowSizeInPixels(window, &fb_width, &fb_height); if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) { ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); diff --git a/examples/example_sdl3_wgpu/main.cpp b/examples/example_sdl3_wgpu/main.cpp index 4aee4ef6a..bc1787829 100644 --- a/examples/example_sdl3_wgpu/main.cpp +++ b/examples/example_sdl3_wgpu/main.cpp @@ -155,7 +155,7 @@ int main(int, char**) // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] // React to changes in screen size int width, height; - SDL_GetWindowSize(window, &width, &height); + SDL_GetWindowSizeInPixels(window, &width, &height); if (width != wgpu_surface_width || height != wgpu_surface_height) ResizeSurface(width, height); From 9e6c0416fe1bc159b4fe029415a047d910cfb918 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 29 Jun 2026 18:26:59 +0200 Subject: [PATCH 080/254] Backends: GLFW,SDL2,SDL3: added wayland/driver marker in io.BackendPlatformName. + Stored bd->IsWayland variable for e.g. #7433 --- backends/imgui_impl_glfw.cpp | 16 ++++++++-------- backends/imgui_impl_sdl2.cpp | 12 +++++++----- backends/imgui_impl_sdl3.cpp | 13 ++++++++----- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index 6af9f5d10..678129a07 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -213,7 +213,7 @@ struct ImGui_ImplGlfw_Data bool IsWayland; bool InstalledCallbacks; bool CallbacksChainForAllWindows; - char BackendPlatformName[32]; + char BackendPlatformName[40]; #ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 const char* CanvasSelector; #endif @@ -676,7 +676,13 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw // Setup backend capabilities flags ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)(); - snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_glfw (%d)", GLFW_VERSION_COMBINED); + bd->Context = ImGui::GetCurrentContext(); + bd->Window = window; + bd->Time = 0.0; + bd->IsWayland = ImGui_ImplGlfw_IsWayland(); + ImGui_ImplGlfw_ContextMap_Add(window, bd->Context); + + snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_glfw (%d)%s", GLFW_VERSION_COMBINED, bd->IsWayland ? " (Wayland)" : ""); io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = bd->BackendPlatformName; #if GLFW_HAS_CREATECURSOR @@ -684,12 +690,6 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw #endif io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) - bd->Context = ImGui::GetCurrentContext(); - bd->Window = window; - bd->Time = 0.0; - bd->IsWayland = ImGui_ImplGlfw_IsWayland(); - ImGui_ImplGlfw_ContextMap_Add(window, bd->Context); - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); #if GLFW_VERSION_COMBINED < 3300 platform_io.Platform_SetClipboardTextFn = [](ImGuiContext*, const char* text) { glfwSetClipboardString(ImGui_ImplGlfw_GetBackendData()->Window, text); }; diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index 3f4a6ace1..4def68dd6 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -147,7 +147,8 @@ struct ImGui_ImplSDL2_Data SDL_Renderer* Renderer; Uint64 Time; char* ClipboardTextData; - char BackendPlatformName[48]; + char BackendPlatformName[64]; + bool IsWayland; // Mouse handling Uint32 MouseWindowID; @@ -499,11 +500,12 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void SDL_version ver_runtime; SDL_VERSION(&ver_compiled); SDL_GetVersion(&ver_runtime); + const char* sdl_video_driver = SDL_GetCurrentVideoDriver(); // Setup backend capabilities flags ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)(); - snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl2 (%u.%u.%u, %u.%u.%u)", - ver_compiled.major, ver_compiled.minor, ver_compiled.patch, ver_runtime.major, ver_runtime.minor, ver_runtime.patch); + snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl2 (%u.%u.%u, %u.%u.%u) (%s)", + ver_compiled.major, ver_compiled.minor, ver_compiled.patch, ver_runtime.major, ver_runtime.minor, ver_runtime.patch, sdl_video_driver); io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = bd->BackendPlatformName; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) @@ -512,16 +514,16 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void bd->Window = window; bd->WindowID = SDL_GetWindowID(window); bd->Renderer = renderer; + bd->IsWayland = strcmp(sdl_video_driver, "Wayland") == 0; // Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse() // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) bd->MouseCanUseGlobalState = false; bd->MouseCaptureMode = ImGui_ImplSDL2_MouseCaptureMode_Disabled; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE - const char* sdl_backend = SDL_GetCurrentVideoDriver(); const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; for (const char* item : capture_and_global_state_whitelist) - if (strncmp(sdl_backend, item, strlen(item)) == 0) + if (strncmp(sdl_video_driver, item, strlen(item)) == 0) { bd->MouseCanUseGlobalState = true; bd->MouseCaptureMode = (strcmp(item, "x11") == 0) ? ImGui_ImplSDL2_MouseCaptureMode_EnabledAfterDrag : ImGui_ImplSDL2_MouseCaptureMode_Enabled; diff --git a/backends/imgui_impl_sdl3.cpp b/backends/imgui_impl_sdl3.cpp index bab2d2312..05f6498f1 100644 --- a/backends/imgui_impl_sdl3.cpp +++ b/backends/imgui_impl_sdl3.cpp @@ -111,7 +111,8 @@ struct ImGui_ImplSDL3_Data SDL_Renderer* Renderer; Uint64 Time; char* ClipboardTextData; - char BackendPlatformName[48]; + char BackendPlatformName[64]; + bool IsWayland; // IME handling SDL_Window* ImeWindow; @@ -502,11 +503,13 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void //SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2"); const int ver_linked = SDL_GetVersion(); + const char* sdl_video_driver = SDL_GetCurrentVideoDriver(); // Setup backend capabilities flags ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)(); - snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl3 (%d.%d.%d; %d.%d.%d)", - SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, SDL_VERSIONNUM_MAJOR(ver_linked), SDL_VERSIONNUM_MINOR(ver_linked), SDL_VERSIONNUM_MICRO(ver_linked)); + snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl3 (%d.%d.%d; %d.%d.%d) (%s)", + SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, SDL_VERSIONNUM_MAJOR(ver_linked), SDL_VERSIONNUM_MINOR(ver_linked), SDL_VERSIONNUM_MICRO(ver_linked), + sdl_video_driver); io.BackendPlatformUserData = (void*)bd; io.BackendPlatformName = bd->BackendPlatformName; io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) @@ -515,16 +518,16 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void bd->Window = window; bd->WindowID = SDL_GetWindowID(window); bd->Renderer = renderer; + bd->IsWayland = strcmp(sdl_video_driver, "wayland") == 0; // Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse() // ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) bd->MouseCanUseGlobalState = false; bd->MouseCaptureMode = ImGui_ImplSDL3_MouseCaptureMode_Disabled; #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE - const char* sdl_backend = SDL_GetCurrentVideoDriver(); const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" }; for (const char* item : capture_and_global_state_whitelist) - if (strncmp(sdl_backend, item, strlen(item)) == 0) + if (strncmp(sdl_video_driver, item, strlen(item)) == 0) { bd->MouseCanUseGlobalState = true; bd->MouseCaptureMode = (strcmp(item, "x11") == 0) ? ImGui_ImplSDL3_MouseCaptureMode_EnabledAfterDrag : ImGui_ImplSDL3_MouseCaptureMode_Enabled; From e408733eccd89b7a2c0f0013a202cc48e199465b Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 1 Jul 2026 12:38:00 +0200 Subject: [PATCH 081/254] BeginMenu(), MenuItem() use NextItemData.Flags + Fixed typo in comment. (#9456) --- imgui.cpp | 2 +- imgui_widgets.cpp | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f6fb61dcd..addbe5e9a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1519,7 +1519,7 @@ ImGuiStyle::ImGuiStyle() GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. ImageRounding = 0.0f; // Rounding of Image() calls. - ImageBorderSize = 0.0f; // Thickness of border around tabs. + ImageBorderSize = 0.0f; // Thickness of border around Image() calls. TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c8458eba1..7b770b433 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9382,7 +9382,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // This is only done for items for the menu set and not the full parent window. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) - PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); + g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck; // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). @@ -9445,8 +9445,6 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) } const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; - if (menuset_is_open) - PopItemFlag(); bool want_open = false; bool want_open_nav_init = false; @@ -9609,7 +9607,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) - PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); + g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck; // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. @@ -9680,8 +9678,6 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (!enabled) EndDisabled(); PopID(); - if (menuset_is_open) - PopItemFlag(); return pressed; } From 0edad6d097bed1706515faf2ba4762a70fcffec8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 1 Jul 2026 14:42:23 +0200 Subject: [PATCH 082/254] Internals: track WasSelected/WasSoleSelected data for ActiveId. (#8337) --- imgui.cpp | 7 +++++++ imgui_internal.h | 5 +++++ imgui_widgets.cpp | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index addbe5e9a..b237f956f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4234,6 +4234,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) ActiveIdIsAlive = 0; ActiveIdTimer = 0.0f; ActiveIdIsJustActivated = false; + ActiveIdWasSelected = ActiveIdWasSoleSelected = false; ActiveIdAllowOverlap = false; ActiveIdNoClearOnFocusLoss = false; ActiveIdHasBeenPressedBefore = false; @@ -4250,6 +4251,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation)); LastActiveId = 0; LastActiveIdTimer = 0.0f; + LastActiveIdWasSelected = LastActiveIdWasSoleSelected = false; LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0; @@ -5629,6 +5631,11 @@ void ImGui::NewFrame() // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore) if (g.ActiveId) g.ActiveIdTimer += g.IO.DeltaTime; + if (g.ActiveId && g.ActiveId == g.LastActiveId) + { + g.LastActiveIdWasSelected = g.ActiveIdWasSelected; + g.LastActiveIdWasSoleSelected = g.ActiveIdWasSoleSelected; + } g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; g.ActiveIdIsAlive = 0; diff --git a/imgui_internal.h b/imgui_internal.h index 7f07a5439..299287e65 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1940,6 +1940,7 @@ struct IMGUI_API ImGuiMultiSelectTempData bool NavIdPassedBy; bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem. bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set. + bool IsSoleOrUnknownSelectionSize; ImGuiMultiSelectTempData() { Clear(); } void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation. @@ -2281,6 +2282,8 @@ struct ImGuiContext ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame) float ActiveIdTimer; bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdWasSelected; // Active ID was selected at the time of activating + bool ActiveIdWasSoleSelected; // Active ID was sole selection at the time of activating bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) bool ActiveIdNoClearOnFocusLoss; // Disable losing active id if the active id window gets unfocused. bool ActiveIdHasBeenPressedBefore; // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without pressing twice). Used by range_select branch. @@ -2297,6 +2300,8 @@ struct ImGuiContext ImGuiDataTypeStorage ActiveIdValueOnActivation; // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX. ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. + bool LastActiveIdWasSelected; + bool LastActiveIdWasSoleSelected; // Key/Input Ownership + Shortcut Routing system // - The idea is that instead of "eating" a given key, we can link to an owner. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7b770b433..ace655d4d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7489,6 +7489,11 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + if (g.ActiveId == id && g.ActiveIdIsJustActivated) + { + g.ActiveIdWasSelected = was_selected; + g.ActiveIdWasSoleSelected = was_selected && (!is_multi_select || g.CurrentMultiSelect->IsSoleOrUnknownSelectionSize); + } // Render if (is_visible) @@ -8116,6 +8121,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel storage->LastSelectionSize = 0; } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; + ms->IsSoleOrUnknownSelectionSize = (storage->LastSelectionSize == 1) || (storage->LastSelectionSize == -1); //ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) From 279d04f7a3e17c4349715a0e7fde455e65facf4a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 1 Jul 2026 18:25:21 +0200 Subject: [PATCH 083/254] Added GetItemClickedCountWithSingleClickDelay(), io.MouseSingleClickDelay. (#8337) --- docs/CHANGELOG.txt | 9 +++++++ imgui.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++++- imgui.h | 8 +++--- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 12c359693..54a7422bb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -100,6 +100,15 @@ Other Changes: instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] - TreeNode: - Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) +- Inputs: + - Added GetItemClickedCountWithSingleClickDelay() helper for easy disambiguation + between single-click and double-click for actions that needs single-click to do + something other than selection. (#8337) + - Returns 1 on single-click but delayed by io.MouseSingleClickDelay. + - Returns 2 on double-click, and 2+ on subsequent repeated cicks. + - Added io.MouseSingleClickDelay to configure default delayed single click delay when + using GetItemClickedCountWithSingleClickDelay() or IsMouseReleasedWithDelay(). (#8337) + Note that io.MouseSingleClickDelay is always > io.MouseDoubleClickTime. - Style: - Added style.MenuItemRounding, ImGuiStyleVar_MenuItemRounding. (#7589, #9375, #9453) - Added style.SelectableRounding, ImGuiStyleVar_SelectableRounding. (#7589, #9375, #9453) diff --git a/imgui.cpp b/imgui.cpp index b237f956f..a7d03caa4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1692,6 +1692,7 @@ ImGuiIO::ImGuiIO() // Inputs Behaviors MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; + MouseSingleClickDelay = 0.50f; MouseDragThreshold = 6.0f; KeyRepeatDelay = 0.275f; KeyRepeatRate = 0.050f; @@ -9427,6 +9428,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // - IsMouseReleased() // - IsMouseDoubleClicked() // - GetMouseClickedCount() +// - GetItemClickedCountWithSingleClickDelay() // - IsMouseHoveringRect() [Internal] // - IsMouseDragPastThreshold() [Internal] // - IsMouseDragging() @@ -10006,6 +10008,8 @@ bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } + +// Prefer higher-level helper GetItemClickedCountWithSingleClickDelay() // Use if you absolutely need to distinguish single-click from double-click by introducing a delay. // Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test. // This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. @@ -10013,8 +10017,12 @@ bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown)); + if (IsMouseDown(button)) + return false; + if (delay < 0.0f) + delay = g.IO.MouseSingleClickDelay; const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]); - return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay); + return (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay); } bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) @@ -10038,6 +10046,62 @@ int ImGui::GetMouseClickedCount(ImGuiMouseButton button) return g.IO.MouseClickedCount[button]; } +// FIXME: This is close to what BeginDragDropSource() is doing, maybe rework. +static ImGuiID LastItemOverlayButtonForNullId(ImGuiMouseButton mouse_button) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.LastItemData.ID == 0); + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetIDFromRectangle(g.LastItemData.Rect); + if (g.IO.MouseClicked[mouse_button] && ImGui::ItemHoverable(g.LastItemData.Rect, id, g.LastItemData.ItemFlags)) + { + ImGui::SetActiveID(id, window); + ImGui::FocusWindow(window); + } + else if (g.ActiveId == id) + { + ImGui::KeepAliveID(id); + if (!g.IO.MouseDown[mouse_button]) + ImGui::ClearActiveID(); + } + return id; +} + +// [BETA] Building block for disambiguation between single-click and double-click. +// - Returns 1 on single-click but delayed by io.MouseSingleClickDelay (which is always > io.MouseDoubleClickTime) after mouse release. +// - Returns 2+ on double-click and subsequent repeated clicks. +// In order use that to replicate Windows Explorer's "click on label to rename after a delay", +// When the function returns 1 for a delayed single click, you can add with further tests: +// - If you want to test that the mouse position AT THE TIME of the click (before the delay): you can use the 'io.MouseClickedPos[mouse_button]' position. +// - If you want to test that the mouse position is still over the item at the end of delay: you can use '&& IsItemHovered()'. +// - If you want to test that the item was selected or the sole selection AT THE TIME of the click (before the delay): you can test for 'g.LastActiveIdWasSelected' or 'g.LastActiveIdWasSoleSelected'. +// e.g. +// int click_count = ImGui::GetItemClickedCountWithSingleClickDelay(mouse_button); +// if (click_count == 1 && ImGui::GetCurrentContext()->LastActiveIdWasSoleSelected) +// StartRename(); +// if (click_count == 2) +// Launch(); +int ImGui::GetItemClickedCountWithSingleClickDelay(ImGuiMouseButton mouse_button, float delay) +{ + // Action: double-click and subsequent clicks + ImGuiContext& g = *GImGui; + if (g.IO.MouseClickedCount[mouse_button] >= 2 && IsItemClicked(mouse_button)) + return g.IO.MouseClickedCount[mouse_button]; + + // Action: second click, delayed + ImGuiID id = g.LastItemData.ID; + if (id == 0) + id = LastItemOverlayButtonForNullId(mouse_button); + if (g.LastActiveId == id) + { + if (delay >= 0.0f) + delay = ImMax(delay, g.IO.MouseDoubleClickTime + 0.01f); + if (IsMouseReleasedWithDelay(mouse_button, delay) && g.IO.MouseClickedLastCount[mouse_button] == 1) + return 1; + } + return 0; +} + // Test if mouse cursor is hovering given rectangle // NB- Rectangle is clipped by our current clip setting // NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) @@ -10985,6 +11049,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes); + IM_ASSERT(g.IO.MouseSingleClickDelay > g.IO.MouseDoubleClickTime); // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way. if (g.IO.ConfigErrorRecovery) diff --git a/imgui.h b/imgui.h index 4f659c38f..0d4995699 100644 --- a/imgui.h +++ b/imgui.h @@ -1026,6 +1026,7 @@ namespace ImGui IMGUI_API ImVec2 GetItemRectMax(); // get lower-right bounding rectangle of the last item (screen space) IMGUI_API ImVec2 GetItemRectSize(); // get size of last item IMGUI_API ImGuiItemFlags GetItemFlags(); // get generic flags of last item + IMGUI_API int GetItemClickedCountWithSingleClickDelay(ImGuiMouseButton mouse_button = 0, float delay = -1.0f); // [BETA] building block for disambiguation between single-click and double-click. Returns 1 on single-click but delayed by io.MouseSingleClickDelay after mouse release. Returns 2+ on double-click or repeated clicks. // Viewports // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. @@ -1108,7 +1109,7 @@ namespace ImGui IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1. IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down) IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true) - IMGUI_API bool IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay); // delayed mouse release (use very sparingly!). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test. This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. + IMGUI_API bool IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay=-1.f);// delayed mouse release. Use sparingly. Prefer higher-level helper GetItemClickedCountWithSingleClickDelay(). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test. IMGUI_API int GetMouseClickedCount(ImGuiMouseButton button); // return the number of successive mouse-clicks at the time where a click happen (otherwise 0). IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available @@ -2451,8 +2452,9 @@ struct ImGuiIO // Inputs Behaviors // (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle) - float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. - float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + float MouseDoubleClickTime; // = 0.30f // Time for consecutive clicks to account as a double-click, in seconds. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click or multiple clicks, in pixels. + float MouseSingleClickDelay; // = 0.60f // Time for a delayed click when using GetItemClickedCountWithSingleClickDelay() or IsMouseReleasedWithDelay(), in seconds. Must be > io.MouseDoubleClickTime. float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. float KeyRepeatDelay; // = 0.275f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. From 93dd4c1567a6593a2056395010f77f0da4f72225 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 11:57:31 +0200 Subject: [PATCH 084/254] Backends: Metal4: copy imgui_imp_metal.* to imgui_impl_metal4.*, unmodified. (#9458, #9451) --- backends/imgui_impl_metal4.h | 78 ++++ backends/imgui_impl_metal4.mm | 686 ++++++++++++++++++++++++++++++++++ 2 files changed, 764 insertions(+) create mode 100644 backends/imgui_impl_metal4.h create mode 100644 backends/imgui_impl_metal4.mm diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h new file mode 100644 index 000000000..8af2c8b6b --- /dev/null +++ b/backends/imgui_impl_metal4.h @@ -0,0 +1,78 @@ +// dear imgui: Renderer Backend for Metal +// This needs to be used along with a Platform Backend (e.g. OSX) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +//----------------------------------------------------------------------------- +// ObjC API +//----------------------------------------------------------------------------- + +#ifdef __OBJC__ + +@class MTLRenderPassDescriptor; +@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder; + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device); +IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); +IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, + id commandBuffer, + id commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id device); +IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); + +#endif + +//----------------------------------------------------------------------------- +// C++ API +//----------------------------------------------------------------------------- + +// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file +// More info about using Metal from C++: https://developer.apple.com/metal/cpp/ + +#ifdef IMGUI_IMPL_METAL_CPP +#include +#ifndef __OBJC__ + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device); +IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); +IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, + MTL::CommandBuffer* commandBuffer, + MTL::RenderCommandEncoder* commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); +IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); + +#endif +#endif + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm new file mode 100644 index 000000000..652668c78 --- /dev/null +++ b/backends/imgui_impl_metal4.mm @@ -0,0 +1,686 @@ +// dear imgui: Renderer Backend for Metal +// This needs to be used along with a Platform Backend (e.g. OSX) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) +// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) +// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) +// 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343) +// 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) +// 2025-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419). +// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. +// 2022-07-05: Metal: Add dispatch synchronization. +// 2022-06-30: Metal: Use __bridge for ARC based systems. +// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler. +// 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts. +// 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code). +// 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file. +// 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464) +// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. +// 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. +// 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-07-05: Metal: Added new Metal backend implementation. + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_metal.h" +#import +#import + +#pragma mark - Support classes + +// A wrapper around a MTLBuffer object that knows the last time it was reused +@interface MetalBuffer : NSObject +@property (nonatomic, strong) id buffer; +@property (nonatomic, assign) double lastReuseTime; +- (instancetype)initWithBuffer:(id)buffer; +@end + +// An object that encapsulates the data necessary to uniquely identify a +// render pipeline state. These are used as cache keys. +@interface FramebufferDescriptor : NSObject +@property (nonatomic, assign) unsigned long sampleCount; +@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; +@property (nonatomic, assign) MTLPixelFormat depthPixelFormat; +@property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; +- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; +@end + +@interface MetalTexture : NSObject +@property (nonatomic, strong) id metalTexture; +- (instancetype)initWithTexture:(id)metalTexture; +@end + +// A singleton that stores long-lived objects that are needed by the Metal +// renderer backend. Stores the render pipeline state cache and the default +// font texture, and manages the reusable buffer cache. +@interface MetalContext : NSObject +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id depthStencilState; +@property (nonatomic, strong) id samplerStateLinear; +@property (nonatomic, strong) id samplerStateNearest; +@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient +@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors +@property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, strong) NSObject* bufferCacheLock; +@property (nonatomic, assign) double lastBufferCachePurge; +- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; +- (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; +@end + +struct ImGui_ImplMetal_Data +{ + MetalContext* SharedMetalContext; + id RenderCommandEncoder; + + ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); } + +static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } + +#ifdef IMGUI_IMPL_METAL_CPP + +#pragma mark - Dear ImGui Metal C++ Backend API + +bool ImGui_ImplMetal_Init(MTL::Device* device) +{ + return ImGui_ImplMetal_Init((__bridge id)(device)); +} + +void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) +{ + ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); +} + +void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, + MTL::CommandBuffer* commandBuffer, + MTL::RenderCommandEncoder* commandEncoder) +{ + ImGui_ImplMetal_RenderDrawData(draw_data, + (__bridge id)(commandBuffer), + (__bridge id)(commandEncoder)); + +} + +bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) +{ + return ImGui_ImplMetal_CreateDeviceObjects((__bridge id)(device)); +} + +#endif // #ifdef IMGUI_IMPL_METAL_CPP + +#pragma mark - Dear ImGui Metal Backend API + +void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?"); +#ifdef IMGUI_IMPL_METAL_CPP + bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease]; +#else + bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; +#endif + if (bd->SharedMetalContext.depthStencilState == nil) + ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); +} + +static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id commandBuffer, + id commandEncoder, id renderPipelineState, + MetalBuffer* vertexBuffer, size_t vertexBufferOffset) +{ + IM_UNUSED(commandBuffer); + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + [commandEncoder setCullMode:MTLCullModeNone]; + [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + MTLViewport viewport = + { + .originX = 0.0, + .originY = 0.0, + .width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x), + .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .znear = 0.0, + .zfar = 1.0 + }; + [commandEncoder setViewport:viewport]; + + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + float N = (float)viewport.znear; + float F = (float)viewport.zfar; + const float ortho_projection[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, 1/(F-N), 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), N/(F-N), 1.0f }, + }; + [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1]; + + [commandEncoder setRenderPipelineState:renderPipelineState]; + [commandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; + + [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:vertexBufferOffset atIndex:0]; +} + +// Draw callbacks +static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplMetal_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; } +static void ImGui_ImplMetal_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateNearest atIndex:0]; } + +// Metal Render function. +void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + MetalContext* ctx = bd->SharedMetalContext; + + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) + return; + + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplMetal_UpdateTexture(tex); + + // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame + // The hit rate for this cache should be very near 100%. + id renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; + if (renderPipelineState == nil) + { + // No luck; make a new render pipeline state + renderPipelineState = [ctx renderPipelineStateForFramebufferDescriptor:ctx.framebufferDescriptor device:commandBuffer.device]; + + // Cache render pipeline state for later reuse + ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; + } + + size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx); + MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; + MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; + + bd->RenderCommandEncoder = commandEncoder; + ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + size_t vertexBufferOffset = 0; + size_t indexBufferOffset = 0; + for (const ImDrawList* draw_list : draw_data->CmdLists) + { + memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + + for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + // User callback, registered via ImDrawList::AddCallback() + if (pcmd->UserCallback == ImGui_ImplMetal_DrawCallback_ResetRenderState) + ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); + else + pcmd->UserCallback(draw_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + + // Clamp to viewport as setScissorRect() won't accept values that are off bounds + if (clip_min.x < 0.0f) { clip_min.x = 0.0f; } + if (clip_min.y < 0.0f) { clip_min.y = 0.0f; } + if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; } + if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) + continue; + if (pcmd->ElemCount == 0) // drawIndexedPrimitives() validation doesn't accept this + continue; + + // Apply scissor/clipping rectangle + MTLScissorRect scissorRect = + { + .x = NSUInteger(clip_min.x), + .y = NSUInteger(clip_min.y), + .width = NSUInteger(clip_max.x - clip_min.x), + .height = NSUInteger(clip_max.y - clip_min.y) + }; + [commandEncoder setScissorRect:scissorRect]; + + // Bind texture, Draw + 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]; + [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle + indexCount:pcmd->ElemCount + indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 + indexBuffer:indexBuffer.buffer + indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; + } + } + + vertexBufferOffset += (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert); + indexBufferOffset += (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx); + } + + MetalContext* sharedMetalContext = bd->SharedMetalContext; + [commandBuffer addCompletedHandler:^(id) + { + @synchronized(sharedMetalContext.bufferCacheLock) + { + [sharedMetalContext.bufferCache addObject:vertexBuffer]; + [sharedMetalContext.bufferCache addObject:indexBuffer]; + } + }]; + bd->RenderCommandEncoder = nil; +} + +static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) +{ + if (MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData)) + { + IM_ASSERT(backend_tex.metalTexture == (__bridge id)(void*)(intptr_t)tex->TexID); + backend_tex.metalTexture = nil; + + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + tex->BackendUserData = nullptr; + } + tex->SetStatus(ImTextureStatus_Destroyed); +} + +void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + if (tex->Status == ImTextureStatus_WantCreate) + { + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + + // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. + // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. + // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. + // You can make that change in your implementation. + MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm + width:(NSUInteger)tex->Width + height:(NSUInteger)tex->Height + mipmapped:NO]; + textureDescriptor.usage = MTLTextureUsageShaderRead; + #if TARGET_OS_OSX || TARGET_OS_MACCATALYST + textureDescriptor.storageMode = MTLStorageModeManaged; + #else + textureDescriptor.storageMode = MTLStorageModeShared; + #endif + id texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; + [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; + MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; + + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)texture); + tex->SetStatus(ImTextureStatus_OK); + tex->BackendUserData = (__bridge_retained void*)(backend_tex); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData); + for (ImTextureRect& r : tex->Updates) + { + [backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h) + mipmapLevel:0 + withBytes:tex->GetPixelsAt(r.x, r.y) + bytesPerRow:(NSUInteger)tex->Width * 4]; + } + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) + { + ImGui_ImplMetal_DestroyTexture(tex); + } +} + +bool ImGui_ImplMetal_CreateDeviceObjects(id device) +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; + depthStencilDescriptor.depthWriteEnabled = NO; + depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; + bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear; + samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear; + bd->SharedMetalContext.samplerStateLinear = [device newSamplerStateWithDescriptor:samplerDescriptor]; + samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; + samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest; + bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor]; +#ifdef IMGUI_IMPL_METAL_CPP + [samplerDescriptor release]; + [depthStencilDescriptor release]; +#endif + + return true; +} + +void ImGui_ImplMetal_DestroyDeviceObjects() +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + ImGui_ImplMetal_DestroyTexture(tex); + + [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; + bd->SharedMetalContext.samplerStateLinear = nil; + bd->SharedMetalContext.samplerStateNearest = nil; +} + +bool ImGui_ImplMetal_Init(id device) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + + ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_metal"; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal_DrawCallback_SetSamplerNearest; + + bd->SharedMetalContext = [[MetalContext alloc] init]; + bd->SharedMetalContext.device = device; + + return true; +} + +void ImGui_ImplMetal_Shutdown() +{ + ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + IM_UNUSED(bd); + IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + ImGui_ImplMetal_DestroyDeviceObjects(); + ImGui_ImplMetal_DestroyBackendData(); + + io.BackendRendererName = nullptr; + io.BackendRendererUserData = nullptr; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); +} + +#pragma mark - MetalBuffer implementation + +@implementation MetalBuffer +- (instancetype)initWithBuffer:(id)buffer +{ + if ((self = [super init])) + { + _buffer = buffer; + _lastReuseTime = GetMachAbsoluteTimeInSeconds(); + } + return self; +} +@end + +#pragma mark - FramebufferDescriptor implementation + +@implementation FramebufferDescriptor +- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor +{ + if ((self = [super init])) + { + _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; + _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; + _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone*)zone +{ + FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; + copy.sampleCount = self.sampleCount; + copy.colorPixelFormat = self.colorPixelFormat; + copy.depthPixelFormat = self.depthPixelFormat; + copy.stencilPixelFormat = self.stencilPixelFormat; + return copy; +} + +- (NSUInteger)hash +{ + NSUInteger sc = _sampleCount & 0x3; + NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger df = _depthPixelFormat & 0x3FF; + NSUInteger sf = _stencilPixelFormat & 0x3FF; + NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + return hash; +} + +- (BOOL)isEqual:(id)object +{ + FramebufferDescriptor* other = object; + if (![other isKindOfClass:[FramebufferDescriptor class]]) + return NO; + return other.sampleCount == self.sampleCount && + other.colorPixelFormat == self.colorPixelFormat && + other.depthPixelFormat == self.depthPixelFormat && + other.stencilPixelFormat == self.stencilPixelFormat; +} + +@end + +#pragma mark - MetalTexture implementation + +@implementation MetalTexture +- (instancetype)initWithTexture:(id)metalTexture +{ + if ((self = [super init])) + self.metalTexture = metalTexture; + return self; +} + +@end + +#pragma mark - MetalContext implementation + +@implementation MetalContext +- (instancetype)init +{ + if ((self = [super init])) + { + self.renderPipelineStateCache = [NSMutableDictionary dictionary]; + self.bufferCache = [NSMutableArray array]; + self.bufferCacheLock = [[NSObject alloc] init]; + _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); + } + return self; +} + +- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device +{ + double now = GetMachAbsoluteTimeInSeconds(); + + @synchronized(self.bufferCacheLock) + { + // Purge old buffers that haven't been useful for a while + if (now - self.lastBufferCachePurge > 1.0) + { + NSMutableArray* survivors = [NSMutableArray array]; + for (MetalBuffer* candidate in self.bufferCache) + if (candidate.lastReuseTime > self.lastBufferCachePurge) + [survivors addObject:candidate]; + self.bufferCache = [survivors mutableCopy]; + self.lastBufferCachePurge = now; + } + + // See if we have a buffer we can reuse + MetalBuffer* bestCandidate = nil; + for (MetalBuffer* candidate in self.bufferCache) + if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) + bestCandidate = candidate; + + if (bestCandidate != nil) + { + [self.bufferCache removeObject:bestCandidate]; + bestCandidate.lastReuseTime = now; + return bestCandidate; + } + } + + // No luck; make a new buffer + id backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; + return [[MetalBuffer alloc] initWithBuffer:backing]; +} + +// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. +- (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device +{ + NSError* error = nil; + + NSString* shaderSource = @"" + "#include \n" + "using namespace metal;\n" + "\n" + "struct Uniforms {\n" + " float4x4 projectionMatrix;\n" + "};\n" + "\n" + "struct VertexIn {\n" + " float2 position [[attribute(0)]];\n" + " float2 texCoords [[attribute(1)]];\n" + " uchar4 color [[attribute(2)]];\n" + "};\n" + "\n" + "struct VertexOut {\n" + " float4 position [[position]];\n" + " float2 texCoords;\n" + " float4 color;\n" + "};\n" + "\n" + "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n" + " constant Uniforms &uniforms [[buffer(1)]]) {\n" + " VertexOut out;\n" + " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" + " out.texCoords = in.texCoords;\n" + " out.color = float4(in.color) / float4(255.0);\n" + " return out;\n" + "}\n" + "\n" + "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" + " texture2d texture [[texture(0)]],\n" + " sampler textureSampler [[sampler(0)]]) {\n" + " half4 texColor = texture.sample(textureSampler, in.texCoords);\n" + " return half4(in.color) * texColor;\n" + "}\n"; + + id library = [device newLibraryWithSource:shaderSource options:nil error:&error]; + if (library == nil) + { + NSLog(@"Error: failed to create Metal library: %@", error); + return nil; + } + + id vertexFunction = [library newFunctionWithName:@"vertex_main"]; + id fragmentFunction = [library newFunctionWithName:@"fragment_main"]; + + if (vertexFunction == nil || fragmentFunction == nil) + { + NSLog(@"Error: failed to find Metal shader functions in library: %@", error); + return nil; + } + + MTLVertexDescriptor* vertexDescriptor = [MTLVertexDescriptor vertexDescriptor]; + vertexDescriptor.attributes[0].offset = offsetof(ImDrawVert, pos); + vertexDescriptor.attributes[0].format = MTLVertexFormatFloat2; // position + vertexDescriptor.attributes[0].bufferIndex = 0; + vertexDescriptor.attributes[1].offset = offsetof(ImDrawVert, uv); + vertexDescriptor.attributes[1].format = MTLVertexFormatFloat2; // texCoords + vertexDescriptor.attributes[1].bufferIndex = 0; + vertexDescriptor.attributes[2].offset = offsetof(ImDrawVert, col); + vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color + vertexDescriptor.attributes[2].bufferIndex = 0; + vertexDescriptor.layouts[0].stepRate = 1; + vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; + vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert); + + MTLRenderPipelineDescriptor* pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineDescriptor.vertexFunction = vertexFunction; + pipelineDescriptor.fragmentFunction = fragmentFunction; + pipelineDescriptor.vertexDescriptor = vertexDescriptor; + pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; + pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; + pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; + pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; + pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; + pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; + + id renderPipelineState = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error]; + if (error != nil) + NSLog(@"Error: failed to create Metal pipeline state: %@", error); + + return renderPipelineState; +} + +@end + +//----------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE From fc6395365d4acf55d75d248bb2e82bd962c6bb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Am=C3=A9lie=20Heinrich?= Date: Tue, 30 Jun 2026 01:15:44 +0200 Subject: [PATCH 085/254] Backends: Metal4: Added Metal 4 backend. Added SDL3+Metal4 example. (#9458, #9451) --- .github/workflows/build.yml | 3 + .gitignore | 1 + backends/imgui_impl_metal4.h | 60 ++-- backends/imgui_impl_metal4.mm | 387 ++++++++++++++------------ examples/example_sdl3_metal4/Makefile | 48 ++++ examples/example_sdl3_metal4/main.mm | 239 ++++++++++++++++ 6 files changed, 513 insertions(+), 225 deletions(-) create mode 100644 examples/example_sdl3_metal4/Makefile create mode 100644 examples/example_sdl3_metal4/main.mm diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4d3ed459..cc84ae2d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -572,6 +572,9 @@ jobs: - name: Build macOS example_sdl2_metal run: make -C examples/example_sdl2_metal + - name: Build macOS example_sdl3_metal4 + run: make -C examples/example_sdl3_metal4 + - name: Build macOS example_sdl2_opengl2 run: make -C examples/example_sdl2_opengl2 if: github.event_name == 'workflow_run' diff --git a/.gitignore b/.gitignore index a8b4e38e3..83c0ab423 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,7 @@ examples/example_sdl2_opengl3/example_sdl2_opengl3 examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2 examples/example_sdl2_vulkan/example_sdl2_vulkan examples/example_sdl3_metal/example_sdl3_metal +examples/example_sdl3_metal4/example_sdl3_metal4 examples/example_sdl3_opengl3/example_sdl3_opengl3 examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3 examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3 diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h index 8af2c8b6b..7f9ec2d16 100644 --- a/backends/imgui_impl_metal4.h +++ b/backends/imgui_impl_metal4.h @@ -1,8 +1,8 @@ -// dear imgui: Renderer Backend for Metal +// dear imgui: Renderer Backend for Metal 4 // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: -// [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: User texture binding. Use 'MTLTexture.gpuResourceID' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). @@ -24,55 +24,31 @@ #ifdef __OBJC__ -@class MTLRenderPassDescriptor; -@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder; +@class MTL4RenderPassDescriptor; +@protocol MTLDevice, MTL4CommandBuffer, MTL4RenderCommandEncoder, MTL4CommandQueue; +// framesInFlight must match the number of frames your application keeps in flight (e.g. the size of your own +// command buffer/allocator ring). The backend uses it to size its own per-frame-in-flight resources (constant +// buffer, vertex/index buffer cache) so the CPU never overwrites a slot the GPU may still be reading. // Follow "Getting Started" link and check examples/ folder to learn about using backends! -IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id device); -IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor); -IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, - id commandBuffer, - id commandEncoder); +IMGUI_IMPL_API bool ImGui_ImplMetal4_Init(id device, id commandQueue, int framesInFlight); +IMGUI_IMPL_API void ImGui_ImplMetal4_Shutdown(); +// frameInFlightIndex must match the slot you use to index your own per-frame-in-flight resources +// (e.g. the same index used to pick your command buffer/allocator), and must be < framesInFlight passed to Init(). +IMGUI_IMPL_API void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex); +IMGUI_IMPL_API void ImGui_ImplMetal4_RenderDrawData(ImDrawData* drawData, + id commandBuffer, + id commandEncoder); // Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id device); -IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); +IMGUI_IMPL_API bool ImGui_ImplMetal4_CreateDeviceObjects(id device); +IMGUI_IMPL_API void ImGui_ImplMetal4_DestroyDeviceObjects(); // (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. -IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); +IMGUI_IMPL_API void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex); #endif -//----------------------------------------------------------------------------- -// C++ API -//----------------------------------------------------------------------------- - -// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file -// More info about using Metal from C++: https://developer.apple.com/metal/cpp/ - -#ifdef IMGUI_IMPL_METAL_CPP -#include -#ifndef __OBJC__ - -// Follow "Getting Started" link and check examples/ folder to learn about using backends! -IMGUI_IMPL_API bool ImGui_ImplMetal_Init(MTL::Device* device); -IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor); -IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, - MTL::CommandBuffer* commandBuffer, - MTL::RenderCommandEncoder* commandEncoder); - -// Called by Init/NewFrame/Shutdown -IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); -IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); - -// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. -IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex); - -#endif -#endif - //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 652668c78..1114b470b 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -1,4 +1,4 @@ -// dear imgui: Renderer Backend for Metal +// dear imgui: Renderer Backend for Metal 4 // This needs to be used along with a Platform Backend (e.g. OSX) // Implemented features: @@ -14,43 +14,26 @@ // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). // - Introduction, links and more at the top of imgui.cpp +// FIXME: Metal-cpp support +// FIXME?: Texture view pool support + // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) -// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) -// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) -// 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343) -// 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) -// 2025-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419). -// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. -// 2022-07-05: Metal: Add dispatch synchronization. -// 2022-06-30: Metal: Use __bridge for ARC based systems. -// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler. -// 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts. -// 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code). -// 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file. -// 2021-08-24: Metal: Fixed a crash when clipping rect larger than framebuffer is submitted. (#4464) -// 2021-05-19: Metal: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement) -// 2021-02-18: Metal: Change blending equation to preserve alpha in output buffer. -// 2021-01-25: Metal: Fixed texture storage mode when building on Mac Catalyst. -// 2019-05-29: Metal: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. -// 2019-04-30: Metal: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. -// 2019-02-11: Metal: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. -// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-07-05: Metal: Added new Metal backend implementation. +// 2026-29-06: Metal 4: Added new Metal 4 backend implementation. #include "imgui.h" #ifndef IMGUI_DISABLE -#include "imgui_impl_metal.h" +#include "imgui_impl_metal4.h" #import #import -#pragma mark - Support classes +#pragma mark - Support classes and structs + +struct ImGui_Metal4_ConstantData +{ + float ModelViewProjectionMatrix[4][4]; +}; -// A wrapper around a MTLBuffer object that knows the last time it was reused @interface MetalBuffer : NSObject @property (nonatomic, strong) id buffer; @property (nonatomic, assign) double lastReuseTime; @@ -64,7 +47,7 @@ @property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; -- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; +- (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor; @end @interface MetalTexture : NSObject @@ -77,83 +60,63 @@ // font texture, and manages the reusable buffer cache. @interface MetalContext : NSObject @property (nonatomic, strong) id device; +@property (nonatomic, strong) id commandQueue; @property (nonatomic, strong) id depthStencilState; +@property (nonatomic, strong) id argumentTable; @property (nonatomic, strong) id samplerStateLinear; @property (nonatomic, strong) id samplerStateNearest; -@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient -@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors -@property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, strong) id residencySet; +@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; +@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; +@property (nonatomic, assign) NSUInteger framesInFlight; +@property (nonatomic, assign) NSUInteger currentFrameSlot; +@property (nonatomic, strong) NSArray>* constantBuffers; +@property (nonatomic, assign) ImGui_Metal4_ConstantData** constantBufferContentsArray; +@property (nonatomic, strong) NSMutableArray*>* bufferCaches; @property (nonatomic, strong) NSObject* bufferCacheLock; @property (nonatomic, assign) double lastBufferCachePurge; +- (id)currentConstantBuffer; +- (ImGui_Metal4_ConstantData*)currentConstantBufferContents; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; @end -struct ImGui_ImplMetal_Data +struct ImGui_ImplMetal4_Data { - MetalContext* SharedMetalContext; - id RenderCommandEncoder; + MetalContext* SharedMetalContext; + id RenderCommandEncoder; - ImGui_ImplMetal_Data() { memset((void*)this, 0, sizeof(*this)); } + ImGui_ImplMetal4_Data() { memset((void*)this, 0, sizeof(*this)); } }; -static ImGui_ImplMetal_Data* ImGui_ImplMetal_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } -static void ImGui_ImplMetal_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal_GetBackendData()); } +static ImGui_ImplMetal4_Data* ImGui_ImplMetal4_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplMetal4_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +static void ImGui_ImplMetal4_DestroyBackendData(){ IM_DELETE(ImGui_ImplMetal4_GetBackendData()); } static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } -#ifdef IMGUI_IMPL_METAL_CPP - -#pragma mark - Dear ImGui Metal C++ Backend API - -bool ImGui_ImplMetal_Init(MTL::Device* device) -{ - return ImGui_ImplMetal_Init((__bridge id)(device)); -} - -void ImGui_ImplMetal_NewFrame(MTL::RenderPassDescriptor* renderPassDescriptor) -{ - ImGui_ImplMetal_NewFrame((__bridge MTLRenderPassDescriptor*)(renderPassDescriptor)); -} - -void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, - MTL::CommandBuffer* commandBuffer, - MTL::RenderCommandEncoder* commandEncoder) -{ - ImGui_ImplMetal_RenderDrawData(draw_data, - (__bridge id)(commandBuffer), - (__bridge id)(commandEncoder)); - -} - -bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) -{ - return ImGui_ImplMetal_CreateDeviceObjects((__bridge id)(device)); -} - -#endif // #ifdef IMGUI_IMPL_METAL_CPP - #pragma mark - Dear ImGui Metal Backend API -void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) +void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal_Init()?"); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + IM_ASSERT(bd != nil && "Context or backend not initialized! Did you call ImGui_ImplMetal4_Init()?"); + IM_ASSERT(frameInFlightIndex < bd->SharedMetalContext.framesInFlight && "frameInFlightIndex out of range! See framesInFlight passed to ImGui_ImplMetal4_Init()."); #ifdef IMGUI_IMPL_METAL_CPP bd->SharedMetalContext.framebufferDescriptor = [[[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]autorelease]; #else bd->SharedMetalContext.framebufferDescriptor = [[FramebufferDescriptor alloc] initWithRenderPassDescriptor:renderPassDescriptor]; #endif + bd->SharedMetalContext.currentFrameSlot = (NSUInteger)frameInFlightIndex; if (bd->SharedMetalContext.depthStencilState == nil) - ImGui_ImplMetal_CreateDeviceObjects(bd->SharedMetalContext.device); + ImGui_ImplMetal4_CreateDeviceObjects(bd->SharedMetalContext.device); } -static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id commandBuffer, - id commandEncoder, id renderPipelineState, +static void ImGui_ImplMetal4_SetupRenderState(ImDrawData* draw_data, id commandBuffer, + id commandEncoder, id renderPipelineState, MetalBuffer* vertexBuffer, size_t vertexBufferOffset) { IM_UNUSED(commandBuffer); - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [commandEncoder setCullMode:MTLCullModeNone]; [commandEncoder setDepthStencilState:bd->SharedMetalContext.depthStencilState]; @@ -184,23 +147,24 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, idSharedMetalContext currentConstantBufferContents]; + memcpy(constantBufferContents->ModelViewProjectionMatrix, ortho_projection, sizeof(ortho_projection)); + id argumentTable = bd->SharedMetalContext.argumentTable; + [argumentTable setAddress:[bd->SharedMetalContext currentConstantBuffer].gpuAddress atIndex:1]; + [argumentTable setAddress:(vertexBuffer.buffer.gpuAddress + vertexBufferOffset) attributeStride:sizeof(ImDrawVert) atIndex:0]; + [argumentTable setSamplerState:bd->SharedMetalContext.samplerStateLinear.gpuResourceID atIndex:0]; + [commandEncoder setArgumentTable:argumentTable atStages:MTLRenderStageVertex | MTLRenderStageFragment]; [commandEncoder setRenderPipelineState:renderPipelineState]; - [commandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; - - [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:vertexBufferOffset atIndex:0]; } -// Draw callbacks -static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. -static void ImGui_ImplMetal_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; } -static void ImGui_ImplMetal_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateNearest atIndex:0]; } +static void ImGui_ImplMetal4_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplMetal4_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [bd->SharedMetalContext.argumentTable setSamplerState:bd->SharedMetalContext.samplerStateLinear.gpuResourceID atIndex:0]; } +static void ImGui_ImplMetal4_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); [bd->SharedMetalContext.argumentTable setSamplerState:bd->SharedMetalContext.samplerStateNearest.gpuResourceID atIndex:0]; } -// Metal Render function. -void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) +void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, id commandBuffer, id commandEncoder) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) @@ -214,7 +178,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id if (draw_data->Textures != nullptr) for (ImTextureData* tex : *draw_data->Textures) if (tex->Status != ImTextureStatus_OK) - ImGui_ImplMetal_UpdateTexture(tex); + ImGui_ImplMetal4_UpdateTexture(tex); // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // The hit rate for this cache should be very near 100%. @@ -234,12 +198,15 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; bd->RenderCommandEncoder = commandEncoder; - ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); + ImGui_ImplMetal4_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + // Before rendering command lists, commit residency set + [bd->SharedMetalContext.residencySet commit]; + // Render command lists size_t vertexBufferOffset = 0; size_t indexBufferOffset = 0; @@ -254,8 +221,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id if (pcmd->UserCallback) { // User callback, registered via ImDrawList::AddCallback() - if (pcmd->UserCallback == ImGui_ImplMetal_DrawCallback_ResetRenderState) - ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); + if (pcmd->UserCallback == ImGui_ImplMetal4_DrawCallback_ResetRenderState) + ImGui_ImplMetal4_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); else pcmd->UserCallback(draw_list, pcmd); } @@ -288,14 +255,19 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id // Bind texture, Draw ImTextureID tex_id = pcmd->GetTexID(); if (tex_id != ImTextureID_Invalid) - [commandEncoder setFragmentTexture:(__bridge id)(void*)(intptr_t)(tex_id) atIndex:0]; + { + id texture = (__bridge id)(void*)(intptr_t)tex_id; + [bd->SharedMetalContext.argumentTable setTexture:texture.gpuResourceID atIndex:0]; + } - [commandEncoder setVertexBufferOffset:(vertexBufferOffset + pcmd->VtxOffset * sizeof(ImDrawVert)) atIndex:0]; + [bd->SharedMetalContext.argumentTable setAddress:(vertexBuffer.buffer.gpuAddress + vertexBufferOffset + (pcmd->VtxOffset * sizeof(ImDrawVert))) attributeStride:sizeof(ImDrawVert) atIndex:0]; + + size_t indexBufferCmdOffset = indexBufferOffset + (pcmd->IdxOffset * sizeof(ImDrawIdx)); [commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle - indexCount:pcmd->ElemCount - indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 - indexBuffer:indexBuffer.buffer - indexBufferOffset:indexBufferOffset + pcmd->IdxOffset * sizeof(ImDrawIdx)]; + indexCount:pcmd->ElemCount + indexType:sizeof(ImDrawIdx) == 2 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32 + indexBuffer:indexBuffer.buffer.gpuAddress + indexBufferCmdOffset + indexBufferLength:indexBuffer.buffer.length - indexBufferCmdOffset]; } } @@ -304,18 +276,16 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id } MetalContext* sharedMetalContext = bd->SharedMetalContext; - [commandBuffer addCompletedHandler:^(id) + @synchronized(sharedMetalContext.bufferCacheLock) { - @synchronized(sharedMetalContext.bufferCacheLock) - { - [sharedMetalContext.bufferCache addObject:vertexBuffer]; - [sharedMetalContext.bufferCache addObject:indexBuffer]; - } - }]; + NSMutableArray* slotCache = sharedMetalContext.bufferCaches[sharedMetalContext.currentFrameSlot]; + [slotCache addObject:vertexBuffer]; + [slotCache addObject:indexBuffer]; + } bd->RenderCommandEncoder = nil; } -static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) +static void ImGui_ImplMetal4_DestroyTexture(ImTextureData* tex) { if (MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData)) { @@ -329,9 +299,9 @@ static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex) tex->SetStatus(ImTextureStatus_Destroyed); } -void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) +void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); if (tex->Status == ImTextureStatus_WantCreate) { // Create and upload new texture to graphics system @@ -348,12 +318,10 @@ void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) height:(NSUInteger)tex->Height mipmapped:NO]; textureDescriptor.usage = MTLTextureUsageShaderRead; - #if TARGET_OS_OSX || TARGET_OS_MACCATALYST - textureDescriptor.storageMode = MTLStorageModeManaged; - #else textureDescriptor.storageMode = MTLStorageModeShared; - #endif + id texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor]; + [bd->SharedMetalContext.residencySet addAllocation:texture]; [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4]; MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture]; @@ -378,18 +346,29 @@ void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) { - ImGui_ImplMetal_DestroyTexture(tex); + ImGui_ImplMetal4_DestroyTexture(tex); } } -bool ImGui_ImplMetal_CreateDeviceObjects(id device) +bool ImGui_ImplMetal4_CreateDeviceObjects(id device) { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); + + MTLResidencySetDescriptor* residencySetDescriptor = [[MTLResidencySetDescriptor alloc] init]; + residencySetDescriptor.initialCapacity = 1000; + + NSError* error = nil; + bd->SharedMetalContext.residencySet = [device newResidencySetWithDescriptor:residencySetDescriptor error:&error]; + IM_ASSERT(bd->SharedMetalContext.residencySet != nil && error == nil); + + [bd->SharedMetalContext.commandQueue addResidencySet:bd->SharedMetalContext.residencySet]; + MTLDepthStencilDescriptor* depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; depthStencilDescriptor.depthWriteEnabled = NO; depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init]; + samplerDescriptor.supportArgumentBuffers = YES; samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear; samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear; samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear; @@ -398,61 +377,84 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id device) samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest; samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest; bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor]; -#ifdef IMGUI_IMPL_METAL_CPP - [samplerDescriptor release]; - [depthStencilDescriptor release]; -#endif + + NSMutableArray>* constantBuffers = [NSMutableArray array]; + ImGui_Metal4_ConstantData** constantBufferContentsArray = (ImGui_Metal4_ConstantData**)malloc(sizeof(ImGui_Metal4_ConstantData*) * bd->SharedMetalContext.framesInFlight); + for (NSUInteger i = 0; i < bd->SharedMetalContext.framesInFlight; i++) + { + id constantBuffer = [device newBufferWithLength:sizeof(ImGui_Metal4_ConstantData) options:MTLResourceStorageModeShared]; + [constantBuffers addObject:constantBuffer]; + constantBufferContentsArray[i] = (ImGui_Metal4_ConstantData*)constantBuffer.contents; + [bd->SharedMetalContext.residencySet addAllocation:constantBuffer]; + } + bd->SharedMetalContext.constantBuffers = constantBuffers; + bd->SharedMetalContext.constantBufferContentsArray = constantBufferContentsArray; + + MTL4ArgumentTableDescriptor* argumentTableDescriptor = [[MTL4ArgumentTableDescriptor alloc] init]; + argumentTableDescriptor.maxBufferBindCount = 8; + argumentTableDescriptor.maxTextureBindCount = 8; + argumentTableDescriptor.maxSamplerStateBindCount = 8; + argumentTableDescriptor.supportAttributeStrides = YES; // required: vertex buffer is bound via setAddress:stride:atIndex: for stage_in fetch + + bd->SharedMetalContext.argumentTable = [device newArgumentTableWithDescriptor:argumentTableDescriptor error:&error]; + IM_ASSERT(bd->SharedMetalContext.argumentTable != nil && error == nil); return true; } -void ImGui_ImplMetal_DestroyDeviceObjects() +void ImGui_ImplMetal4_DestroyDeviceObjects() { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); // Destroy all textures for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) if (tex->RefCount == 1) - ImGui_ImplMetal_DestroyTexture(tex); + ImGui_ImplMetal4_DestroyTexture(tex); [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; bd->SharedMetalContext.samplerStateLinear = nil; bd->SharedMetalContext.samplerStateNearest = nil; } -bool ImGui_ImplMetal_Init(id device) +bool ImGui_ImplMetal4_Init(id device, id commandQueue, int framesInFlight) { ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); + IM_ASSERT(framesInFlight > 0 && "framesInFlight must be at least 1!"); - ImGui_ImplMetal_Data* bd = IM_NEW(ImGui_ImplMetal_Data)(); + ImGui_ImplMetal4_Data* bd = IM_NEW(ImGui_ImplMetal4_Data)(); io.BackendRendererUserData = (void*)bd; - io.BackendRendererName = "imgui_impl_metal"; + io.BackendRendererName = "imgui_impl_metal4"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal_DrawCallback_ResetRenderState; - platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal_DrawCallback_SetSamplerLinear; - platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal4_DrawCallback_ResetRenderState; + platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal4_DrawCallback_SetSamplerLinear; + platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal4_DrawCallback_SetSamplerNearest; bd->SharedMetalContext = [[MetalContext alloc] init]; bd->SharedMetalContext.device = device; - + bd->SharedMetalContext.commandQueue = commandQueue; + bd->SharedMetalContext.framesInFlight = (NSUInteger)framesInFlight; + NSMutableArray*>* bufferCaches = [NSMutableArray array]; + for (NSUInteger i = 0; i < framesInFlight; i++) + [bufferCaches addObject:[NSMutableArray array]]; + bd->SharedMetalContext.bufferCaches = bufferCaches; return true; } -void ImGui_ImplMetal_Shutdown() +void ImGui_ImplMetal4_Shutdown() { - ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); + ImGui_ImplMetal4_Data* bd = ImGui_ImplMetal4_GetBackendData(); IM_UNUSED(bd); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - ImGui_ImplMetal_DestroyDeviceObjects(); - ImGui_ImplMetal_DestroyBackendData(); + ImGui_ImplMetal4_DestroyDeviceObjects(); + ImGui_ImplMetal4_DestroyBackendData(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; @@ -460,8 +462,6 @@ void ImGui_ImplMetal_Shutdown() platform_io.ClearRendererHandlers(); } -#pragma mark - MetalBuffer implementation - @implementation MetalBuffer - (instancetype)initWithBuffer:(id)buffer { @@ -477,7 +477,7 @@ void ImGui_ImplMetal_Shutdown() #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor -- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor +- (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { @@ -542,39 +542,57 @@ void ImGui_ImplMetal_Shutdown() if ((self = [super init])) { self.renderPipelineStateCache = [NSMutableDictionary dictionary]; - self.bufferCache = [NSMutableArray array]; self.bufferCacheLock = [[NSObject alloc] init]; _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; } +- (void)dealloc +{ + free(_constantBufferContentsArray); +} + +- (id)currentConstantBuffer +{ + return self.constantBuffers[self.currentFrameSlot]; +} + +- (ImGui_Metal4_ConstantData*)currentConstantBufferContents +{ + return self.constantBufferContentsArray[self.currentFrameSlot]; +} + - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { double now = GetMachAbsoluteTimeInSeconds(); + NSMutableArray* slotCache = self.bufferCaches[self.currentFrameSlot]; @synchronized(self.bufferCacheLock) { // Purge old buffers that haven't been useful for a while if (now - self.lastBufferCachePurge > 1.0) { - NSMutableArray* survivors = [NSMutableArray array]; - for (MetalBuffer* candidate in self.bufferCache) - if (candidate.lastReuseTime > self.lastBufferCachePurge) - [survivors addObject:candidate]; - self.bufferCache = [survivors mutableCopy]; + for (NSMutableArray* cache in self.bufferCaches) + { + NSMutableArray* survivors = [NSMutableArray array]; + for (MetalBuffer* candidate in cache) + if (candidate.lastReuseTime > self.lastBufferCachePurge) + [survivors addObject:candidate]; + [cache setArray:survivors]; + } self.lastBufferCachePurge = now; } - // See if we have a buffer we can reuse + // See if we have a buffer we can reuse, from this frame-in-flight slot's own cache MetalBuffer* bestCandidate = nil; - for (MetalBuffer* candidate in self.bufferCache) + for (MetalBuffer* candidate in slotCache) if (candidate.buffer.length >= length && (bestCandidate == nil || bestCandidate.lastReuseTime > candidate.lastReuseTime)) bestCandidate = candidate; if (bestCandidate != nil) { - [self.bufferCache removeObject:bestCandidate]; + [slotCache removeObject:bestCandidate]; bestCandidate.lastReuseTime = now; return bestCandidate; } @@ -582,51 +600,54 @@ void ImGui_ImplMetal_Shutdown() // No luck; make a new buffer id backing = [device newBufferWithLength:length options:MTLResourceStorageModeShared]; + [self.residencySet addAllocation:backing]; return [[MetalBuffer alloc] initWithBuffer:backing]; } -// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. +const char* shaderCode = R"( +#include +using namespace metal; + +struct Uniforms { + float4x4 projectionMatrix; +}; + +struct VertexIn { + float2 position [[attribute(0)]]; + float2 texCoords [[attribute(1)]]; + uchar4 color [[attribute(2)]]; +}; + +struct VertexOut { + float4 position [[position]]; + float2 texCoords; + float4 color; +}; + +vertex VertexOut vertex_main(VertexIn in [[stage_in]], + constant Uniforms &uniforms [[buffer(1)]]) +{ + VertexOut out; + out.position = uniforms.projectionMatrix * float4(in.position, 0, 1); + out.texCoords = in.texCoords; + out.color = float4(in.color) / float4(255.0); + return out; +} + +fragment half4 fragment_main(VertexOut in [[stage_in]], + texture2d texture [[texture(0)]], + sampler textureSampler [[sampler(0)]]) +{ + half4 texColor = texture.sample(textureSampler, in.texCoords); + return half4(in.color) * texColor; +} +)"; + - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device { NSError* error = nil; - NSString* shaderSource = @"" - "#include \n" - "using namespace metal;\n" - "\n" - "struct Uniforms {\n" - " float4x4 projectionMatrix;\n" - "};\n" - "\n" - "struct VertexIn {\n" - " float2 position [[attribute(0)]];\n" - " float2 texCoords [[attribute(1)]];\n" - " uchar4 color [[attribute(2)]];\n" - "};\n" - "\n" - "struct VertexOut {\n" - " float4 position [[position]];\n" - " float2 texCoords;\n" - " float4 color;\n" - "};\n" - "\n" - "vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n" - " constant Uniforms &uniforms [[buffer(1)]]) {\n" - " VertexOut out;\n" - " out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n" - " out.texCoords = in.texCoords;\n" - " out.color = float4(in.color) / float4(255.0);\n" - " return out;\n" - "}\n" - "\n" - "fragment half4 fragment_main(VertexOut in [[stage_in]],\n" - " texture2d texture [[texture(0)]],\n" - " sampler textureSampler [[sampler(0)]]) {\n" - " half4 texColor = texture.sample(textureSampler, in.texCoords);\n" - " return half4(in.color) * texColor;\n" - "}\n"; - - id library = [device newLibraryWithSource:shaderSource options:nil error:&error]; + id library = [device newLibraryWithSource:[NSString stringWithUTF8String:shaderCode] options:nil error:&error]; if (library == nil) { NSLog(@"Error: failed to create Metal library: %@", error); diff --git a/examples/example_sdl3_metal4/Makefile b/examples/example_sdl3_metal4/Makefile new file mode 100644 index 000000000..55c83416c --- /dev/null +++ b/examples/example_sdl3_metal4/Makefile @@ -0,0 +1,48 @@ +# +# You will need SDL3 (http://www.libsdl.org): +# brew install sdl3 +# + +#CXX = g++ +#CXX = clang++ + +EXE = example_sdl3_metal4 +IMGUI_DIR = ../.. +SOURCES = main.mm +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl3.cpp $(IMGUI_DIR)/backends/imgui_impl_metal4.mm +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) + +LIBS = -framework Metal -framework MetalKit -framework Cocoa -framework IOKit -framework CoreVideo -framework QuartzCore +LIBS += `pkg-config --libs sdl3` +LIBS += -L/usr/local/lib -L/opt/local/lib + +CXXFLAGS += `pkg-config --cflags sdl3` +CXXFLAGS += -I/usr/local/include -I/opt/local/include +CXXFLAGS += -std=c++11 -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +CXXFLAGS += -Wall -Wformat +CFLAGS = $(CXXFLAGS) + +%.o:%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:%.mm + $(CXX) $(CXXFLAGS) -ObjC++ -fobjc-weak -fobjc-arc -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.mm + $(CXX) $(CXXFLAGS) -ObjC++ -fobjc-weak -fobjc-arc -c -o $@ $< + +all: $(EXE) + @echo Build complete + +$(EXE): $(OBJS) + $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS) + +clean: + rm -f $(EXE) $(OBJS) diff --git a/examples/example_sdl3_metal4/main.mm b/examples/example_sdl3_metal4/main.mm new file mode 100644 index 000000000..f0bb8f0ca --- /dev/null +++ b/examples/example_sdl3_metal4/main.mm @@ -0,0 +1,239 @@ +// Dear ImGui: standalone example application for SDL3 + Metal 4 +// (SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#include "imgui.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_metal4.h" +#include // printf, fprintf +#include + +#import +#import + +#define FRAMES_IN_FLIGHT 3 + +// Main code +int main(int, char**) +{ + // Setup SDL + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + { + printf("Error: SDL_Init(): %s\n", SDL_GetError()); + return 1; + } + + // Create SDL window graphics context + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + SDL_WindowFlags window_flags = SDL_WINDOW_METAL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+Metal example", (int)(1280 * main_scale), (int)(800 * main_scale), window_flags); + if (window == nullptr) + { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return 1; + } + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + SDL_ShowWindow(window); + + // Create Metal device _before_ creating the view/layer + id metalDevice = MTLCreateSystemDefaultDevice(); + if (!metalDevice) + { + printf("Error: failed to create Metal device.\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + SDL_MetalView view = SDL_Metal_CreateView(window); + CAMetalLayer* layer = (__bridge CAMetalLayer*)SDL_Metal_GetLayer(view); + layer.device = metalDevice; + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + + id commandQueue = [layer.device newMTL4CommandQueue]; + id sharedEvent = [layer.device newSharedEvent]; + MTL4RenderPassDescriptor* renderPassDescriptor = [MTL4RenderPassDescriptor new]; + + [commandQueue addResidencySet:layer.residencySet]; + + id commandBuffers[FRAMES_IN_FLIGHT]; + id commandAllocators[FRAMES_IN_FLIGHT]; + for (int i = 0; i < FRAMES_IN_FLIGHT; i++) + { + commandAllocators[i] = [layer.device newCommandAllocator]; + commandBuffers[i] = [layer.device newCommandBuffer]; + } + uint32_t frameIndex = 0; + uint32_t frameInFlight = 0; + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) + + // Setup Platform/Renderer backends + ImGui_ImplMetal4_Init(layer.device, commandQueue, FRAMES_IN_FLIGHT); + ImGui_ImplSDL3_InitForMetal(window); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + float clear_color[4] = { 0.45f, 0.55f, 0.60f, 1.00f }; + + // Main loop + bool done = false; + while (!done) + { + @autoreleasepool + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT) + done = true; + if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) + { + SDL_Delay(10); + continue; + } + + int width, height; + SDL_GetWindowSizeInPixels(window, &width, &height); + + layer.drawableSize = CGSizeMake(width, height); + id drawable = [layer nextDrawable]; + + uint64_t waitValue = (frameIndex >= FRAMES_IN_FLIGHT) ? (frameIndex - FRAMES_IN_FLIGHT + 1) : 0; + [sharedEvent waitUntilSignaledValue:waitValue timeoutMS:1000]; + [commandAllocators[frameInFlight] reset]; + [commandBuffers[frameInFlight] beginCommandBufferWithAllocator:commandAllocators[frameInFlight]]; + + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color[0] * clear_color[3], clear_color[1] * clear_color[3], clear_color[2] * clear_color[3], clear_color[3]); + renderPassDescriptor.colorAttachments[0].texture = drawable.texture; + renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + id renderEncoder = [commandBuffers[frameInFlight] renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder pushDebugGroup:@"ImGui demo"]; + + // Start the Dear ImGui frame + ImGui_ImplMetal4_NewFrame(renderPassDescriptor, frameInFlight); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + ImGui_ImplMetal4_RenderDrawData(draw_data, commandBuffers[frameInFlight], renderEncoder); + + [renderEncoder popDebugGroup]; + [renderEncoder endEncoding]; + [commandBuffers[frameInFlight] endCommandBuffer]; + + id to_commit[] = { commandBuffers[frameInFlight] }; + + [commandQueue waitForDrawable:drawable]; + [commandQueue commit:to_commit count:1]; + + [commandQueue signalEvent:sharedEvent value:frameIndex + 1]; + [commandQueue signalDrawable:drawable]; + [drawable present]; + + frameIndex++; + frameInFlight = (frameInFlight + 1) % FRAMES_IN_FLIGHT; + } + } + + // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] + ImGui_ImplMetal4_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} From caddd26fb915c4fc8030b30ed72c8be26123f3ab Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 12:09:06 +0200 Subject: [PATCH 086/254] Backends: Metal4: minor tweaks/comments. (#9458, #9451) --- backends/imgui_impl_metal4.h | 3 +++ backends/imgui_impl_metal4.mm | 8 ++++---- docs/BACKENDS.md | 1 + docs/CHANGELOG.txt | 4 ++++ docs/EXAMPLES.md | 4 ++++ docs/README.md | 2 +- examples/example_sdl3_metal4/main.mm | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h index 7f9ec2d16..6bfea6e56 100644 --- a/backends/imgui_impl_metal4.h +++ b/backends/imgui_impl_metal4.h @@ -5,6 +5,9 @@ // [X] Renderer: User texture binding. Use 'MTLTexture.gpuResourceID' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// Missing features or Issues: +// [ ] Metal-cpp support. +// [ ] Texture view pool support? Reevaluate which type to use for ImtextureID. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 1114b470b..5f8d4983a 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -5,6 +5,9 @@ // [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// Missing features or Issues: +// [ ] Metal-cpp support. +// [ ] Texture view pool support? Reevaluate which type to use for ImtextureID. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -14,12 +17,9 @@ // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). // - Introduction, links and more at the top of imgui.cpp -// FIXME: Metal-cpp support -// FIXME?: Texture view pool support - // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-29-06: Metal 4: Added new Metal 4 backend implementation. +// 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) #include "imgui.h" #ifndef IMGUI_DISABLE diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 17e34dd3a..2fb9cfa71 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -89,6 +89,7 @@ List of Renderer Backends: imgui_impl_dx11.cpp ; DirectX11 imgui_impl_dx12.cpp ; DirectX12 imgui_impl_metal.mm ; Metal (ObjC or C++) + imgui_impl_metal4.mm ; Metal 4 (ObjC or C++) imgui_impl_opengl2.cpp ; OpenGL 2 (legacy fixed pipeline. Don't use with modern OpenGL code!) imgui_impl_opengl3.cpp ; OpenGL 3/4, OpenGL ES 2/3, WebGL imgui_impl_sdlgpu3.cpp ; SDL_GPU (portable 3D graphics API of SDL3) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 54a7422bb..bbef65980 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -127,6 +127,9 @@ Other Changes: - Misc: - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: + - Metal4: + - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] + Note that Metal-cpp is not yet supported. - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] @@ -139,6 +142,7 @@ Other Changes: - Examples: - SDL2/SDL3: use `SDL_GetWindowSizeInPixels()` to create frame-buffers. Fixes issues with non-fractional framebuffer size on Wayland. (#8761, #9124) [@billtran1632001] + - SDL3+Metal4: added new example. (#9458, #9451) [@AmelieHeinrich] ----------------------------------------------------------------------- VERSION 1.92.8 (Released 2026-05-12) diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 676d745bc..3d410477b 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -164,6 +164,10 @@ SDL3 + DirectX11 examples, Windows only.
SDL3 + Metal example, Mac only.
= main.cpp + imgui_impl_sdl3.cpp + imgui_impl_metal.mm
+[example_sdl3_metal4/](https://github.com/ocornut/imgui/blob/master/examples/example_sdl3_metal/)
+SDL3 + Metal4 example, Mac only.
+= main.cpp + imgui_impl_sdl3.cpp + imgui_impl_metal4.mm
+ [example_sdl3_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_sdl3_opengl3/)
SDL3 (Win32, Mac, Linux, etc.) + OpenGL3+/ES2/ES3 example.
= main.cpp + imgui_impl_sdl3.cpp + imgui_impl_opengl3.cpp
diff --git a/docs/README.md b/docs/README.md index cc73e28ec..91bc92cde 100644 --- a/docs/README.md +++ b/docs/README.md @@ -141,7 +141,7 @@ Integrating Dear ImGui within your custom engine is a matter of mainly 1) wiring - Generally, **make sure to spend time reading the [FAQ](https://www.dearimgui.com/faq), comments, and the examples applications!** Officially maintained backends (in repository): -- Renderers: DirectX9, DirectX10, DirectX11, DirectX12, Metal, OpenGL/ES/ES2, SDL_GPU, SDL_Renderer2/3, Vulkan, WebGPU. +- Renderers: DirectX9, DirectX10, DirectX11, DirectX12, Metal 3/4, OpenGL/ES/ES2, SDL_GPU, SDL_Renderer2/3, Vulkan, WebGPU. - Platforms: GLFW, SDL2/SDL3, Win32, Glut, OSX, Android. - Frameworks: Allegro5, Emscripten. diff --git a/examples/example_sdl3_metal4/main.mm b/examples/example_sdl3_metal4/main.mm index f0bb8f0ca..d80b4dde0 100644 --- a/examples/example_sdl3_metal4/main.mm +++ b/examples/example_sdl3_metal4/main.mm @@ -32,7 +32,7 @@ int main(int, char**) // Create SDL window graphics context float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); SDL_WindowFlags window_flags = SDL_WINDOW_METAL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY; - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+Metal example", (int)(1280 * main_scale), (int)(800 * main_scale), window_flags); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+Metal4 example", (int)(1280 * main_scale), (int)(800 * main_scale), window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); From e9ea19086b4701b37237f53e1ab457a8a6b90f92 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 14:12:32 +0200 Subject: [PATCH 087/254] CI: fixed Android build on Ubuntu-latest by expliciting Gradle version to < 9.x. --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc84ae2d3..3ec0800a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -654,8 +654,12 @@ jobs: name: Build - Android steps: - - uses: actions/checkout@v6 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 + with: + gradle-version: '8.14.5' + - uses: actions/checkout@v6 - name: Build example_android_opengl3 run: | cd examples/example_android_opengl3/android From 6824e2c2e4b3e1314f8b84ccd870197cc038972c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 14:26:46 +0200 Subject: [PATCH 088/254] Examples: Android: update to AGP 9.2.0 to support Gradle 9.6.0. --- .github/workflows/build.yml | 8 ++++---- docs/CHANGELOG.txt | 2 ++ examples/example_android_opengl3/android/app/build.gradle | 4 ---- .../android/gradle/libs.versions.toml | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ec0800a1..c67d981c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -654,10 +654,10 @@ jobs: name: Build - Android steps: - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v6 - with: - gradle-version: '8.14.5' + #- name: Setup Gradle + # uses: gradle/actions/setup-gradle@v6 + # with: + # gradle-version: '8.14.5' - uses: actions/checkout@v6 - name: Build example_android_opengl3 diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index bbef65980..1119b7ab6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -140,10 +140,12 @@ Other Changes: when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends to spawn multiple-thread to manage OpenGL. (#9403) - Examples: + - Android: update to AGP 9.2.0 to support Gradle 9.6.0. - SDL2/SDL3: use `SDL_GetWindowSizeInPixels()` to create frame-buffers. Fixes issues with non-fractional framebuffer size on Wayland. (#8761, #9124) [@billtran1632001] - SDL3+Metal4: added new example. (#9458, #9451) [@AmelieHeinrich] + ----------------------------------------------------------------------- VERSION 1.92.8 (Released 2026-05-12) ----------------------------------------------------------------------- diff --git a/examples/example_android_opengl3/android/app/build.gradle b/examples/example_android_opengl3/android/app/build.gradle index e142f1917..65555fb6e 100644 --- a/examples/example_android_opengl3/android/app/build.gradle +++ b/examples/example_android_opengl3/android/app/build.gradle @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) } android { @@ -25,9 +24,6 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = '11' - } externalNativeBuild { cmake { path file('../../CMakeLists.txt') diff --git a/examples/example_android_opengl3/android/gradle/libs.versions.toml b/examples/example_android_opengl3/android/gradle/libs.versions.toml index 82971810b..43d9021eb 100644 --- a/examples/example_android_opengl3/android/gradle/libs.versions.toml +++ b/examples/example_android_opengl3/android/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.12.0" -kotlin = "2.0.21" +agp = "9.2.0" +kotlin = "2.3.21" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 8fc25800e0f4164c6edc4e9b00cc0e8d988d3b3c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 15:02:59 +0200 Subject: [PATCH 089/254] Fonts, Context: assert that ImFontAtlas has no other references when destroying an owned atlas. (#9426) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1119b7ab6..aca07b0ad 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -95,6 +95,7 @@ Other Changes: calling ClearFonts() during rendering. - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) + - Destroying an ImGui context using a ImFontAtlas checks that the later has no references. - Nav: - Fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] diff --git a/imgui.cpp b/imgui.cpp index a7d03caa4..2feefc854 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4495,10 +4495,14 @@ void ImGui::Shutdown() for (ImFontAtlas* atlas : g.FontAtlases) { UnregisterFontAtlas(atlas); - if (atlas->RefCount == 0 && atlas->OwnerContext == &g) + if (atlas->OwnerContext == &g) { - atlas->Locked = false; - IM_DELETE(atlas); + IM_ASSERT(atlas->RefCount == 0 && "Destroying context owning a ImFontAtlas which is still used elsewhere!"); + if (atlas->RefCount == 0) + { + atlas->Locked = false; + IM_DELETE(atlas); + } } } g.DrawListSharedData.TempBuffer.clear(); From 7d43c74b591ff539097138a7a6596d3df7610252 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 15:13:49 +0200 Subject: [PATCH 090/254] CI: update PVS studio. Reduce Windows CI workload for typical push. --- .github/workflows/build.yml | 5 ++++- .github/workflows/static-analysis.yml | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c67d981c8..dd1d0d47e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -116,7 +116,7 @@ jobs: cl.exe /D_USRDLL /D_WINDLL /I. example_single_file.cpp /LD /FeImGui.dll /link cl.exe /DIMGUI_API=__declspec(dllimport) -DIMGUI_IMPL_API= /I. ImGui.lib /Feexample_null.exe examples/example_null/main.cpp - # Win64 examples are more frequently compilted than the Win32 examples. + # Win64 examples are more frequently compiled than the Win32 examples. # More of the Win32 examples requires 'workflow_run' to reduce waste. - name: Build Win32 example_glfw_opengl2 shell: cmd @@ -160,6 +160,7 @@ jobs: - name: Build Win32 example_sdl3_opengl3 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj /p:Platform=Win32 /p:Configuration=Release' + if: github.event_name == 'workflow_run' - name: Build Win32 example_sdl3_sdlgpu3 shell: cmd @@ -200,6 +201,7 @@ jobs: - name: Build Win64 example_glfw_opengl3 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj /p:Platform=x64 /p:Configuration=Release' + if: github.event_name == 'workflow_run' - name: Build Win64 example_glfw_vulkan shell: cmd @@ -228,6 +230,7 @@ jobs: - name: Build Win64 example_sdl2_directx11 shell: cmd run: '"%MSBUILD_PATH%\MSBuild.exe" examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj /p:Platform=x64 /p:Configuration=Release' + if: github.event_name == 'workflow_run' - name: Build Win64 example_sdl3_opengl3 shell: cmd diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 295628368..231d3a972 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -23,9 +23,9 @@ jobs: run: | if [[ "$PVS_STUDIO_LICENSE" != "" ]]; then - wget -q https://files.viva64.com/etc/pubkey.txt + wget -q https://files.pvs-studio.com/etc/pubkey.txt sudo apt-key add pubkey.txt - sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.viva64.com/etc/viva64.list + sudo wget -O /etc/apt/sources.list.d/viva64.list https://files.pvs-studio.com/etc/viva64.list sudo apt-get update sudo apt-get install -y pvs-studio pvs-studio-analyzer credentials -o pvs-studio.lic $PVS_STUDIO_LICENSE From f7e8343ee97726e8e74e19097364dc77ccde79eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 23:00:37 +0200 Subject: [PATCH 091/254] Settings, IO: added io.ConfigIniSettingsSaveLastUsedDate, platform_io.Platform_SessionDate, IMGUI_DISABLE_TIME_FUNCTIONS(). (#9460) cc #437 --- docs/CHANGELOG.txt | 8 ++++++++ imconfig.h | 1 + imgui.cpp | 27 +++++++++++++++++++++++++-- imgui.h | 7 +++++++ imgui_demo.cpp | 3 +++ imgui_internal.h | 18 ++++++++++++++++++ imgui_tables.cpp | 6 ++++++ 7 files changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index aca07b0ad..dba212f14 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -116,6 +116,14 @@ Other Changes: The use of this is discouraged because it can easily create problems rendering e.g. contiguous selection. - Scale the NavCursor border thickness when using large values with `ScallAllSizes()`. +- Settings: + - Windows/Tables settings entries can now record the last used date in YYYYMMDD format, + allowing cleanup tools to run to e.g. delete entries that haven't been used in X months. + - Added bool io.ConfigIniSettingsSaveLastUsedDate to disable saving that info. + - The current system date is fed through ImGuiPlatformIO::Platform_SessionDate, + which is automatically set by a call to time() done during context creation. + - Added IMGUI_DISABLE_TIME_FUNCTIONS to disable setting platform_io.Platform_SessionDate. + A custom backend may still set it manually. - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imconfig.h b/imconfig.h index 851a44b1b..b40db3898 100644 --- a/imconfig.h +++ b/imconfig.h @@ -43,6 +43,7 @@ //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). +//#define IMGUI_DISABLE_TIME_FUNCTIONS // Don't setup default platform_io.Platform_SessionDate value using time(), localtime_r(). //#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. diff --git a/imgui.cpp b/imgui.cpp index 2feefc854..844ca0342 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1243,6 +1243,12 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: // System includes #include // vsnprintf, sscanf, printf #include // intptr_t +#ifndef IMGUI_DISABLE_TIME_FUNCTIONS +#include // time(), localtime_r()/localtime_s() +#if defined(_WIN32) +static tm* localtime_r(const time_t* timep, tm* result) { return localtime_s(result, timep) == 0 ? result : NULL; } +#endif +#endif // [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled #if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) @@ -1677,6 +1683,7 @@ ImGuiIO::ImGuiIO() ConfigWindowsMoveFromTitleBarOnly = false; ConfigWindowsCopyContentsWithCtrlC = false; ConfigScrollbarScrollByPage = true; + ConfigIniSettingsSaveLastUsedDate = true; ConfigMemoryCompactTimer = 60.0f; ConfigDebugIsDebuggerPresent = false; ConfigDebugHighlightIdConflicts = true; @@ -4452,6 +4459,14 @@ void ImGui::Initialize() g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; + // Setup session starting date +#ifndef IMGUI_DISABLE_TIME_FUNCTIONS + const time_t session_time = time(NULL); + struct tm session_datetime = {}; + if (localtime_r(&session_time, &session_datetime)) + g.PlatformIO.Platform_SessionDate = (session_datetime.tm_year + 1900) * 10000 + (session_datetime.tm_mon + 1) * 100 + session_datetime.tm_mday; +#endif + // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID; @@ -5568,6 +5583,7 @@ void ImGui::NewFrame() g.Time += g.IO.DeltaTime; g.FrameCount += 1; + g.SessionDate = ImGuiPackedDate(g.PlatformIO.Platform_SessionDate); g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; g.MenusIdSubmittedThisFrame.resize(0); @@ -6692,6 +6708,7 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s { // Initial window state with e.g. default/arbitrary window position // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + ImGuiContext& g = *GImGui; const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); window->Pos = main_viewport->Pos + ImVec2(60, 60); window->Size = window->SizeFull = ImVec2(0, 0); @@ -6699,6 +6716,7 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s if (settings != NULL) { + settings->LastUsedDate = g.SessionDate; SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); ApplyWindowSettings(window, settings); } @@ -15870,6 +15888,7 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); } else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); } else if (sscanf(line, "IsChild=%d", &i) == 1) { settings->IsChild = (i != 0); } + else if (sscanf(line, "LastUsed=%d", &i) == 1) { settings->LastUsedDate = i; return; } } // Apply to existing windows (if any) @@ -15907,6 +15926,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl settings->IsChild = (window->Flags & ImGuiWindowFlags_ChildWindow) != 0; settings->Collapsed = window->Collapsed; settings->WantDelete = false; + settings->LastUsedDate = g.SessionDate; } // Write to text buffer @@ -15916,7 +15936,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl if (settings->WantDelete) continue; const char* settings_name = settings->GetName(); - buf->appendf("[%s][%s]\n", handler->TypeName, settings_name); + buf->appendf("[%s][%s]\n", handler->TypeName, settings_name); // [Window][name] if (settings->IsChild) { buf->appendf("IsChild=1\n"); @@ -15929,6 +15949,9 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl if (settings->Collapsed) buf->appendf("Collapsed=1\n"); } + if (g.IO.ConfigIniSettingsSaveLastUsedDate) + if (int last_used_date = settings->LastUsedDate.Unpack()) + buf->appendf("LastUsed=%08d\n", last_used_date); buf->append("\n"); } } @@ -17866,7 +17889,7 @@ void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings) { if (settings->WantDelete) BeginDisabled(); - Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", + BulletText("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed); if (settings->WantDelete) EndDisabled(); diff --git a/imgui.h b/imgui.h index 0d4995699..d469e0fb8 100644 --- a/imgui.h +++ b/imgui.h @@ -2436,6 +2436,9 @@ struct ImGuiIO bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. + // Ini Settings + bool ConfigIniSettingsSaveLastUsedDate;// = true // Enable loading/saving last used day (YYYYMMDD) in some .ini struct, making things easier to audit and allowing custom tools to cleanup old data. + // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. @@ -4039,6 +4042,10 @@ struct ImGuiPlatformIO // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point ImWchar Platform_LocaleDecimalPoint; // '.' + // Optional: Platform time/date + // This is automatically filled on startup. Used to store a "last used date" in some .ini structures. Facilitate creating tools to clean up old/unused data. + int Platform_SessionDate; // Integer storing YYYYMMDD e.g. 20261231 corresponding to the beginning of application session. + //------------------------------------------------------------------ // Input - Interface with Renderer Backend //------------------------------------------------------------------ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 1bd300666..91d7e030d 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -538,6 +538,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors."); ImGui::Text("Also see Style->Rendering for rendering options."); + ImGui::SeparatorText("Settings"); + ImGui::Checkbox("io.ConfigIniSettingsSaveLastUsedDate", &io.ConfigIniSettingsSaveLastUsedDate); + // Also read: https://github.com/ocornut/imgui/wiki/Error-Handling ImGui::SeparatorText("Error Handling"); diff --git a/imgui_internal.h b/imgui_internal.h index 299287e65..c7e1a03b4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -138,6 +138,7 @@ Index of this file: struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) struct ImGuiTextIndex; // Maintain a line index for a text buffer. +struct ImGuiPackedDate; // A date in YYYYMMDD format packed into 16-bits // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance @@ -833,6 +834,20 @@ struct ImGuiTextIndex void append(const char* base, int old_size, int new_size); }; +// Helper: ImGuiPackedDate (sizeof() == 2) +// Store a date in a way that is efficient to read/write in text form. If we stored e.g. number of days since Epoch we'd need costlier back and forth. +// This is specifically designed to be able to prune old .ini data. +struct ImGuiPackedDate +{ + ImU16 Year : 7; // Year since 2000 // We can change to another offset e.g. 1970 but this is easier to watch in debugger. + ImU16 Month : 4; // Month (1-12) + ImU16 Day : 5; // Day (1-31) + + ImGuiPackedDate() { Year = Month = Day = 0; } + ImGuiPackedDate(int yyyymmdd) { Year = (ImU16)((yyyymmdd / 10000) - 2000); Month = (ImU16)((yyyymmdd / 100) % 100); Day = (ImU16)(yyyymmdd % 100); } // Pack + int Unpack() const { return (Year && Month && Day) ? ((Year + 2000) * 10000) + (Month * 100) + Day : 0; } // Unpack +}; + // Helper: ImGuiStorage IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key); @@ -2018,6 +2033,7 @@ struct ImGuiWindowSettings ImGuiID ID; ImVec2ih Pos; ImVec2ih Size; + ImGuiPackedDate LastUsedDate; bool Collapsed : 1; bool IsChild : 1; bool WantApply : 1; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) @@ -2533,6 +2549,7 @@ struct ImGuiContext ImVector UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[]. // Settings + ImGuiPackedDate SessionDate; // Packed copy of platform_io.Platform_SessionDate, when valid. bool SettingsLoaded; float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero ImGuiTextBuffer SettingsIniData; // In memory .ini settings @@ -3208,6 +3225,7 @@ struct ImGuiTableSettings float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. ImGuiTableColumnIdx ColumnsCount; ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher + ImGuiPackedDate LastUsedDate; bool WantApply : 1; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) ImGuiTableSettings() { memset((void*)this, 0, sizeof(*this)); } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b71304f25..1ff368239 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3933,6 +3933,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); } settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount; + settings->LastUsedDate = g.SessionDate; // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings IM_ASSERT(settings->ID == table->ID); @@ -4001,6 +4002,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; + settings->LastUsedDate = g.SessionDate; // TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data. } @@ -4163,6 +4165,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, int column_n = 0, r = 0, n = 0; if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } + if (sscanf(line, "LastUsed=%d", &n) == 1) { settings->LastUsedDate = n; return; } if (sscanf(line, "Column %d%n", &column_n, &r) == 1) { @@ -4218,6 +4221,9 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle if (column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); } buf->append("\n"); } + if (g.IO.ConfigIniSettingsSaveLastUsedDate) + if (int last_used_date = settings->LastUsedDate.Unpack()) + buf->appendf("LastUsed=%08d\n", last_used_date); buf->append("\n"); } } From 5fb77dfde1260690af678a1b086288c073db89cd Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jul 2026 18:28:56 +0200 Subject: [PATCH 092/254] Settings: added io.ConfigIniSettingsAutoDiscardMonths, trimming tool in in Metrics->Settings + internal CleanupIniSettings(). (#9460) cc #437 #2564 --- docs/CHANGELOG.txt | 11 +++++--- imgui.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++- imgui.h | 1 + imgui_internal.h | 15 ++++++++++ imgui_tables.cpp | 16 +++++++++++ 5 files changed, 107 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index dba212f14..4647ca97f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -118,12 +118,15 @@ Other Changes: - Scale the NavCursor border thickness when using large values with `ScallAllSizes()`. - Settings: - Windows/Tables settings entries can now record the last used date in YYYYMMDD format, - allowing cleanup tools to run to e.g. delete entries that haven't been used in X months. - - Added bool io.ConfigIniSettingsSaveLastUsedDate to disable saving that info. + allowing tools to run to e.g. delete entries that haven't been used in X months. (#9460) + - Added bool io.ConfigIniSettingsSaveLastUsedDate to disable saving that info. (#9460) + - Added int io.ConfigIniSettingsAutoDiscardMonths to enable a mode where unused settings + are automatically discard after xx months. (#9460) + - Added a trimming tool under Metrics->Settings, along with a yet-unexposed function. - The current system date is fed through ImGuiPlatformIO::Platform_SessionDate, - which is automatically set by a call to time() done during context creation. + which is automatically set by a call to time() done during context creation. (#9460) - Added IMGUI_DISABLE_TIME_FUNCTIONS to disable setting platform_io.Platform_SessionDate. - A custom backend may still set it manually. + A custom backend may still set it manually. (#9460) - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() diff --git a/imgui.cpp b/imgui.cpp index 844ca0342..f1729eb84 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1365,6 +1365,7 @@ static void AddWindowToSortBuffer(ImVector* out_sorted // Settings static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*); +static void WindowSettingsHandler_Cleanup(ImGuiContext*, ImGuiSettingsHandler*, ImGuiSettingsCleanupArgs* args); static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*); @@ -1684,6 +1685,7 @@ ImGuiIO::ImGuiIO() ConfigWindowsCopyContentsWithCtrlC = false; ConfigScrollbarScrollByPage = true; ConfigIniSettingsSaveLastUsedDate = true; + ConfigIniSettingsAutoDiscardMonths = 0; ConfigMemoryCompactTimer = 60.0f; ConfigDebugIsDebuggerPresent = false; ConfigDebugHighlightIdConflicts = true; @@ -4442,6 +4444,7 @@ void ImGui::Initialize() ini_handler.TypeName = "Window"; ini_handler.TypeHash = ImHashStr("Window"); ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll; + ini_handler.CleanupFn = WindowSettingsHandler_Cleanup; ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen; ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine; ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll; @@ -15692,6 +15695,19 @@ void ImGui::ClearIniSettings() handler.ClearAllFn(&g, &handler); } +void ImGui::CleanupIniSettings(ImGuiSettingsCleanupArgs* args) +{ + ImGuiContext& g = *GImGui; + if (g.PlatformIO.Platform_SessionDate == 0) + return; + ImGuiPackedDate discard_older_than_date_p = g.PlatformIO.Platform_SessionDate; + discard_older_than_date_p.SubtractMonths(args->DiscardOlderThanMonths); + args->_DiscardOlderThanDate = discard_older_than_date_p.Unpack(); + for (ImGuiSettingsHandler& handler : g.SettingsHandlers) + if (handler.CleanupFn != NULL) + handler.CleanupFn(&g, &handler, args); +} + void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) { size_t file_data_size = 0; @@ -15770,6 +15786,9 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) memcpy(buf, ini_data, ini_size); // Call post-read handlers + ImGuiSettingsCleanupArgs cleanup_args; + if (g.IO.ConfigIniSettingsAutoDiscardMonths > 0) + cleanup_args.DiscardOlderThanMonths = g.IO.ConfigIniSettingsAutoDiscardMonths; for (ImGuiSettingsHandler& handler : g.SettingsHandlers) if (handler.ApplyAllFn != NULL) handler.ApplyAllFn(&g, &handler); @@ -15866,6 +15885,18 @@ static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandl g.SettingsWindows.clear(); } +static void WindowSettingsHandler_Cleanup(ImGuiContext* ctx, ImGuiSettingsHandler*, ImGuiSettingsCleanupArgs* args) +{ + ImGuiContext& g = *ctx; + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + { + if (args->_DiscardOlderThanDate != 0 && settings->LastUsedDate.Unpack() < args->_DiscardOlderThanDate) + settings->WantDelete = true; + if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && settings->LastUsedDate.IsValid() == false)) + settings->LastUsedDate = g.SessionDate; + } +} + static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { ImGuiID id = ImHashStr(name); @@ -15918,6 +15949,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl if (!settings) { settings = ImGui::CreateNewWindowSettings(window->Name); + settings->LastUsedDate = g.SessionDate; window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); } IM_ASSERT(settings->ID == window->ID); @@ -16724,6 +16756,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + ImGuiPlatformIO& platform_io = g.PlatformIO; ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; if (cfg->ShowDebugLog) ShowDebugLogWindow(&cfg->ShowDebugLog); @@ -17056,8 +17089,36 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("\"%s\"", g.IO.IniFilename); else TextUnformatted(""); - Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings); Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer); + + int highlight_older_than_date = 0; + Text("SessionDate: %d", platform_io.Platform_SessionDate); + BeginDisabled(platform_io.Platform_SessionDate == 0); + Checkbox("Highlight Entries Older Than", &cfg->SettingsHighlightOldEntries); + SetNextItemWidth(GetFontSize() * 8); + SameLine(); + SliderInt("Months", &cfg->SettingsDiscardMonths, 1, 24); + if (cfg->SettingsHighlightOldEntries && cfg->SettingsDiscardMonths > 0) + { + ImGuiPackedDate cutoff_date = platform_io.Platform_SessionDate; + cutoff_date.SubtractMonths(cfg->SettingsDiscardMonths); + highlight_older_than_date = cutoff_date.Unpack(); + SameLine(); + ImGuiSettingsCleanupArgs cleanup_args; + cleanup_args.DiscardOlderThanMonths = cfg->SettingsDiscardMonths; + if (Button("Discard")) + CleanupIniSettings(&cleanup_args); + } + EndDisabled(); + Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings); + + struct ScopedHighlightOlderThan + { + bool Highlight; + ScopedHighlightOlderThan(int cutoff_date, ImGuiPackedDate in_date) { Highlight = cutoff_date != 0 && in_date.Unpack() < cutoff_date; if (Highlight) PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f)); } + ~ScopedHighlightOlderThan() { if (Highlight) PopStyleColor(); } + }; + if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size)) { for (ImGuiSettingsHandler& handler : g.SettingsHandlers) @@ -17067,14 +17128,20 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size())) { for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + { + ScopedHighlightOlderThan scoped_highlight(highlight_older_than_date, settings->LastUsedDate); DebugNodeWindowSettings(settings); + } TreePop(); } if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) { for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + { + ScopedHighlightOlderThan scoped_highlight(highlight_older_than_date, settings->LastUsedDate); DebugNodeTableSettings(settings, NULL); + } TreePop(); } diff --git a/imgui.h b/imgui.h index d469e0fb8..59aba2875 100644 --- a/imgui.h +++ b/imgui.h @@ -2438,6 +2438,7 @@ struct ImGuiIO // Ini Settings bool ConfigIniSettingsSaveLastUsedDate;// = true // Enable loading/saving last used day (YYYYMMDD) in some .ini struct, making things easier to audit and allowing custom tools to cleanup old data. + int ConfigIniSettingsAutoDiscardMonths; // = 0 // [BETA] Set number of months after which unused .ini entries are discarded on load. Require platform_io.Platform_SessionDate to be set. For systems supporting the feature, .ini entries without a LastUsed field will always be discarded! Please report if you are using this. // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') diff --git a/imgui_internal.h b/imgui_internal.h index c7e1a03b4..129981aa5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -98,6 +98,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wmissing-noreturn" // warning: function 'xxx' could be declared with attribute 'noreturn' #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) @@ -845,7 +846,9 @@ struct ImGuiPackedDate ImGuiPackedDate() { Year = Month = Day = 0; } ImGuiPackedDate(int yyyymmdd) { Year = (ImU16)((yyyymmdd / 10000) - 2000); Month = (ImU16)((yyyymmdd / 100) % 100); Day = (ImU16)(yyyymmdd % 100); } // Pack + bool IsValid() { return (Year && Month && Day); } int Unpack() const { return (Year && Month && Day) ? ((Year + 2000) * 10000) + (Month * 100) + Day : 0; } // Unpack + void SubtractMonths(int m) { while (m > 0) { Year -= Month == 1; Month = (Month == 1) ? 12 : Month - 1; m--; } } // FIXME-OPT: Stupid but enough for what we do with it. }; // Helper: ImGuiStorage @@ -2043,6 +2046,14 @@ struct ImGuiWindowSettings char* GetName() { return (char*)(this + 1); } }; +struct ImGuiSettingsCleanupArgs +{ + int DiscardOlderThanMonths = 0; // Enable to discard entries older than XX months. + bool SetCurrentSessionDateToAll = false; // Enable to write current SessionDate to all supporting entries. // Let us know in #9460 if you use this. + bool SetCurrentSessionDateWhenMissingDate = false; // Enable to write current SessionDate to all supporting entries missing a date. // Let us know in #9460 if you use this. + int _DiscardOlderThanDate = 0; // [Internal] +}; + struct ImGuiSettingsHandler { const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' @@ -2053,6 +2064,7 @@ struct ImGuiSettingsHandler void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); // Read: Called for every line of text within an ini entry void (*ApplyAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler); // Read: Called after reading (in registration order) void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf' + void (*CleanupFn) (ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiSettingsCleanupArgs* args);// Cleanup/patch settings void* UserData; ImGuiSettingsHandler() { memset((void*)this, 0, sizeof(*this)); } @@ -2166,6 +2178,8 @@ struct ImGuiMetricsConfig int ShowTablesRectsType = -1; int HighlightMonitorIdx = -1; ImGuiID HighlightViewportID = 0; + int SettingsDiscardMonths = 6; + bool SettingsHighlightOldEntries = false; bool ShowFontPreview = true; }; @@ -3410,6 +3424,7 @@ namespace ImGui IMGUI_API void MarkIniSettingsDirty(); IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); IMGUI_API void ClearIniSettings(); + IMGUI_API void CleanupIniSettings(ImGuiSettingsCleanupArgs* args); // [BETA] Expected to turn into a public API. Please report if you are using this! IMGUI_API void AddSettingsHandler(const ImGuiSettingsHandler* handler); IMGUI_API void RemoveSettingsHandler(const char* type_name); IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 1ff368239..aa0b89efe 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4126,6 +4126,21 @@ static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandle g.SettingsTables.clear(); } +static void TableSettingsHandler_Cleanup(ImGuiContext* ctx, ImGuiSettingsHandler*, ImGuiSettingsCleanupArgs* args) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetMapSize(); i++) + if (ImGuiTable* table = g.Tables.TryGetMapData(i)) + table->SettingsOffset = -1; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + { + if (args->_DiscardOlderThanDate != 0 && settings->LastUsedDate.Unpack() < args->_DiscardOlderThanDate) + settings->ID = 0; + if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && settings->LastUsedDate.IsValid() == false)) + settings->LastUsedDate = g.SessionDate; + } +} + // Apply to existing windows (if any) static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { @@ -4234,6 +4249,7 @@ void ImGui::TableSettingsAddSettingsHandler() ini_handler.TypeName = "Table"; ini_handler.TypeHash = ImHashStr("Table"); ini_handler.ClearAllFn = TableSettingsHandler_ClearAll; + ini_handler.CleanupFn = TableSettingsHandler_Cleanup; ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll; From 5c2160ac85fd24f0e426e970966af9cd979edac8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Jul 2026 16:56:38 +0200 Subject: [PATCH 093/254] Settings, IO: move ConfigDebugIniSettings in same section. --- imgui.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index 59aba2875..f6ac7773f 100644 --- a/imgui.h +++ b/imgui.h @@ -2439,6 +2439,7 @@ struct ImGuiIO // Ini Settings bool ConfigIniSettingsSaveLastUsedDate;// = true // Enable loading/saving last used day (YYYYMMDD) in some .ini struct, making things easier to audit and allowing custom tools to cleanup old data. int ConfigIniSettingsAutoDiscardMonths; // = 0 // [BETA] Set number of months after which unused .ini entries are discarded on load. Require platform_io.Platform_SessionDate to be set. For systems supporting the feature, .ini entries without a LastUsed field will always be discarded! Please report if you are using this. + bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') @@ -2511,9 +2512,6 @@ struct ImGuiIO // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing. - // Option to audit .ini data - bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) - //------------------------------------------------------------------ // Platform Identifiers // (the imgui_impl_xxxx backend files are setting those up for you) From e616ace74dce14f927c808cb82f2a8b21b0d392d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Jul 2026 17:48:11 +0200 Subject: [PATCH 094/254] CI, Backends: Metal4: fixed CI, added comments. (#9458) --- .github/workflows/build.yml | 4 ++-- backends/imgui_impl_metal4.h | 1 + backends/imgui_impl_metal4.mm | 1 + examples/example_sdl3_metal4/main.mm | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd1d0d47e..3980838a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -575,8 +575,8 @@ jobs: - name: Build macOS example_sdl2_metal run: make -C examples/example_sdl2_metal - - name: Build macOS example_sdl3_metal4 - run: make -C examples/example_sdl3_metal4 + #- name: Build macOS example_sdl3_metal4 + # run: make -C examples/example_sdl3_metal4 - name: Build macOS example_sdl2_opengl2 run: make -C examples/example_sdl2_opengl2 diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h index 6bfea6e56..b5ec4ca56 100644 --- a/backends/imgui_impl_metal4.h +++ b/backends/imgui_impl_metal4.h @@ -1,5 +1,6 @@ // dear imgui: Renderer Backend for Metal 4 // This needs to be used along with a Platform Backend (e.g. OSX) +// Metal 4 requires Apple Silicon and macOS 26+. // Implemented features: // [X] Renderer: User texture binding. Use 'MTLTexture.gpuResourceID' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 5f8d4983a..f88d34b4e 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -1,5 +1,6 @@ // dear imgui: Renderer Backend for Metal 4 // This needs to be used along with a Platform Backend (e.g. OSX) +// Metal 4 requires Apple Silicon and macOS 26+. // Implemented features: // [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! diff --git a/examples/example_sdl3_metal4/main.mm b/examples/example_sdl3_metal4/main.mm index d80b4dde0..6debb5133 100644 --- a/examples/example_sdl3_metal4/main.mm +++ b/examples/example_sdl3_metal4/main.mm @@ -1,5 +1,6 @@ // Dear ImGui: standalone example application for SDL3 + Metal 4 // (SDL is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan/Metal graphics context creation, etc.) +// (Metal 4 requires Apple Silicon and macOS 26+.) // Learn about Dear ImGui: // - FAQ https://dearimgui.com/faq From a97714495591ebe947b9b9a87022c7e2b3bcc13a Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Jul 2026 18:42:51 +0200 Subject: [PATCH 095/254] Examples: OpenGL3+GLFW,SDL2,SDL3: improve Wine compatibility by passing empty GLSL version to ImGui_ImplOpenGL3_Init(). (#9427, #6577) --- docs/CHANGELOG.txt | 3 +++ examples/example_glfw_opengl3/main.cpp | 11 ++++------- examples/example_sdl2_opengl3/main.cpp | 11 ++++------- examples/example_sdl3_opengl3/main.cpp | 11 ++++------- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4647ca97f..8ce3d47e7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -153,6 +153,9 @@ Other Changes: to spawn multiple-thread to manage OpenGL. (#9403) - Examples: - Android: update to AGP 9.2.0 to support Gradle 9.6.0. + - OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version + string to ImGui_ImplOpenGL3_Init() to let backend decide of a GLSL version based + on actual GL version obtained. (#9427, #6577) [@perminovVS] - SDL2/SDL3: use `SDL_GetWindowSizeInPixels()` to create frame-buffers. Fixes issues with non-fractional framebuffer size on Wayland. (#8761, #9124) [@billtran1632001] - SDL3+Metal4: added new example. (#9458, #9451) [@AmelieHeinrich] diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp index 20d39fccc..fbcc3e595 100644 --- a/examples/example_glfw_opengl3/main.cpp +++ b/examples/example_glfw_opengl3/main.cpp @@ -41,29 +41,26 @@ int main(int, char**) if (!glfwInit()) return 1; - // Decide GL+GLSL versions + // Select GL version + let the backend select a GLSL version + const char* glsl_version = nullptr; #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 (WebGL 1.0) - const char* glsl_version = "#version 100"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); #elif defined(IMGUI_IMPL_OPENGL_ES3) // GL ES 3.0 + GLSL 300 es (WebGL 2.0) - const char* glsl_version = "#version 300 es"; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); #elif defined(__APPLE__) - // GL 3.2 + GLSL 150 - const char* glsl_version = "#version 150"; + // GL 3.2 + generally GLSL 150 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac #else - // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; + // GL 3.0 + generally GLSL 130 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only diff --git a/examples/example_sdl2_opengl3/main.cpp b/examples/example_sdl2_opengl3/main.cpp index 0d992d47e..e7b101087 100644 --- a/examples/example_sdl2_opengl3/main.cpp +++ b/examples/example_sdl2_opengl3/main.cpp @@ -39,31 +39,28 @@ int main(int, char**) return 1; } - // Decide GL+GLSL versions + // Select GL version + let the backend select a GLSL version + const char* glsl_version = nullptr; #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 (WebGL 1.0) - const char* glsl_version = "#version 100"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #elif defined(IMGUI_IMPL_OPENGL_ES3) // GL ES 3.0 + GLSL 300 es (WebGL 2.0) - const char* glsl_version = "#version 300 es"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #elif defined(__APPLE__) - // GL 3.2 Core + GLSL 150 - const char* glsl_version = "#version 150"; + // GL 3.2 Core + generally GLSL 150 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); #else - // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; + // GL 3.0 + generally GLSL 130 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); diff --git a/examples/example_sdl3_opengl3/main.cpp b/examples/example_sdl3_opengl3/main.cpp index a36cfaba9..e7d22a40c 100644 --- a/examples/example_sdl3_opengl3/main.cpp +++ b/examples/example_sdl3_opengl3/main.cpp @@ -33,31 +33,28 @@ int main(int, char**) return 1; } - // Decide GL+GLSL versions + // Select GL version + let the backend select a GLSL version + const char* glsl_version = nullptr; #if defined(IMGUI_IMPL_OPENGL_ES2) // GL ES 2.0 + GLSL 100 (WebGL 1.0) - const char* glsl_version = "#version 100"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #elif defined(IMGUI_IMPL_OPENGL_ES3) // GL ES 3.0 + GLSL 300 es (WebGL 2.0) - const char* glsl_version = "#version 300 es"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #elif defined(__APPLE__) - // GL 3.2 Core + GLSL 150 - const char* glsl_version = "#version 150"; + // GL 3.2 Core + generally GLSL 150 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); #else - // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; + // GL 3.0 + generally GLSL 130 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); From 4f1ea369eae2510c85e41ef159433e19927ff14c Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Jul 2026 18:57:14 +0200 Subject: [PATCH 096/254] Examples: OpenGL3+Win32: rework OpenGL initialization to allow Wine compatibility. (#9427, #6577) --- docs/CHANGELOG.txt | 1 + examples/example_win32_opengl3/main.cpp | 56 +++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8ce3d47e7..0bc0682f0 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -156,6 +156,7 @@ Other Changes: - OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version string to ImGui_ImplOpenGL3_Init() to let backend decide of a GLSL version based on actual GL version obtained. (#9427, #6577) [@perminovVS] + - OpenGL3+Win32: rework context creation to allow Wine compatibility. (#9427, #6577) [@perminovVS] - SDL2/SDL3: use `SDL_GetWindowSizeInPixels()` to create frame-buffers. Fixes issues with non-fractional framebuffer size on Wayland. (#8761, #9124) [@billtran1632001] - SDL3+Metal4: added new example. (#9458, #9451) [@AmelieHeinrich] diff --git a/examples/example_win32_opengl3/main.cpp b/examples/example_win32_opengl3/main.cpp index d18ea0bd8..b3a4da9c6 100644 --- a/examples/example_win32_opengl3/main.cpp +++ b/examples/example_win32_opengl3/main.cpp @@ -17,12 +17,13 @@ #include #include #include +#include // Data stored per platform window struct WGL_WindowData { HDC hDC; }; // Data -static HGLRC g_hRC; +static HGLRC g_hRC; // Rendering context static WGL_WindowData g_MainWindow; static int g_Width; static int g_Height; @@ -208,8 +209,57 @@ bool CreateDeviceWGL(HWND hWnd, WGL_WindowData* data) ::ReleaseDC(hWnd, hDc); data->hDC = ::GetDC(hWnd); - if (!g_hRC) - g_hRC = wglCreateContext(data->hDC); + if (g_hRC) + return true; + + // Create legacy context + HGLRC tempRC = wglCreateContext(data->hDC); + if (!tempRC) { ::ReleaseDC(hWnd, data->hDC); return false; } + if (!wglMakeCurrent(data->hDC, tempRC)) + { + wglDeleteContext(tempRC); + ::ReleaseDC(hWnd, data->hDC); + return false; + } + + // Get context version + GLint major = 0, minor = 0; + glGetIntegerv(0x821B, &major); // GL_MAJOR_VERSION + glGetIntegerv(0x821C, &minor); // GL_MINOR_VERSION + const char* gl_version_str = (const char*)glGetString(GL_VERSION); + if (major == 0 && minor == 0) + sscanf_s(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." + const GLuint gl_version = (GLuint)(major * 100 + minor * 10); + + // Keep temporary context: already OpenGL 3.0+. + g_hRC = tempRC; + //if (gl_version >= 300) + // return true; + + typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int*); + const PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); + + // GL 3.0 + const int attribs[] = + { + 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB + 0x2092, 0, // WGL_CONTEXT_MINOR_VERSION_ARB + 0x9126, 0x0001, // WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB + 0 + }; + HGLRC newRC = nullptr; + if (wglCreateContextAttribsARB) + newRC = wglCreateContextAttribsARB(data->hDC, 0, attribs); + + // If we managed to create 3.0+ context: use that one and destroy the temporary OpenGL 2.x compatibility context. + if (newRC) + { + wglMakeCurrent(nullptr, nullptr); + wglDeleteContext(tempRC); + g_hRC = newRC; + wglMakeCurrent(data->hDC, g_hRC); + } + return true; } From 776bf2ab0d61e719e58f2c6d27d109ab5dcf2af1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Jul 2026 18:59:37 +0200 Subject: [PATCH 097/254] Examples: OpenGL3+Win32: rework OpenGL initialization to allow Wine compatibility. Fix. (#9427, #6577) Fixed commented out test code. --- examples/example_win32_opengl3/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example_win32_opengl3/main.cpp b/examples/example_win32_opengl3/main.cpp index b3a4da9c6..cba8301f8 100644 --- a/examples/example_win32_opengl3/main.cpp +++ b/examples/example_win32_opengl3/main.cpp @@ -233,8 +233,8 @@ bool CreateDeviceWGL(HWND hWnd, WGL_WindowData* data) // Keep temporary context: already OpenGL 3.0+. g_hRC = tempRC; - //if (gl_version >= 300) - // return true; + if (gl_version >= 300) + return true; typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int*); const PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); From 0e479b95543b736143ebc815fc727ba8c035f5c2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 6 Jul 2026 15:34:13 +0200 Subject: [PATCH 098/254] (Breaking) ColorEdit: obsoleted SetColorEditOptions() function in favor of poking to style.ColorEditFlags. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 8 +++++++- imgui.h | 11 +++++++---- imgui_demo.cpp | 12 ++++++------ imgui_internal.h | 1 - imgui_widgets.cpp | 48 +++++++++++++++++++--------------------------- 6 files changed, 43 insertions(+), 40 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0bc0682f0..c4de5cab9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,9 @@ Breaking Changes: - TreeNode: commented out legacy name `ImGuiTreeNodeFlags_SpanTextWidth` which was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. +- ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in + of directly poking to style.ColorEditFlags. More consistent and easier to discover + Other Changes: diff --git a/imgui.cpp b/imgui.cpp index f1729eb84..5f7ecef50 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/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to style.ColorEditFlags. More consistent and easier to discover. - 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. - 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). - Before: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); @@ -1547,6 +1548,7 @@ ImGuiStyle::ImGuiStyle() DragDropTargetPadding = 3.0f; // Size to expand the drag and drop target from actual target item size. ColorMarkerSize = 3.0f; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. + ColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. InputTextCursorSize = 1.0f; // Thickness of cursor/caret in InputText(). @@ -4356,7 +4358,6 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) TempInputId = 0; memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; - ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; ColorEditCurrentID = ColorEditSavedID = 0; ColorEditSavedHue = ColorEditSavedSat = 0.0f; ColorEditSavedColor = 0; @@ -11073,6 +11074,11 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f && "Invalid style setting!"); // Required otherwise cannot resize from borders. IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); + IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected + IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes); IM_ASSERT(g.IO.MouseSingleClickDelay > g.IO.MouseDoubleClickTime); diff --git a/imgui.h b/imgui.h index f6ac7773f..e38d79b59 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.9 WIP" -#define IMGUI_VERSION_NUM 19284 +#define IMGUI_VERSION_NUM 19285 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -737,7 +737,6 @@ namespace ImGui IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // display a color square/button, hover for details, return true when pressed. - IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. // Widgets: Trees // - TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents. @@ -1918,8 +1917,9 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. - // Defaults Options. You can set application defaults using SetColorEditOptions(). The intent is that you probably don't want to - // override them in most of your calls. Let the user choose via the option menu and/or call SetColorEditOptions() once during startup. + // Defaults Options copied to style.ColorEditFlags during initialization. + // The intent is that you probably don't want to override them in most of your calls. + // Let the user choose via the option menu and/or modify style.ColorEditFlags directly during startup if you want. ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, // [Internal] Masks @@ -2343,6 +2343,7 @@ struct ImGuiStyle float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size. float ColorMarkerSize; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. + ImGuiColorEditFlags ColorEditFlags; // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. float InputTextCursorSize; // Thickness of cursor/caret in InputText(). @@ -4099,6 +4100,8 @@ struct ImGuiPlatformImeData #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.92.9 (from July 2026) + IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // set current options for if you want to select a default format, picker type, etc. User will be able to change those settings, unless you pass the _NoOptions flag to your calls. // OBSOLETED in 1.92.0 (from June 2025) inline void PushFont(ImFont* font) { PushFont(font, font ? font->LegacySize : 0.0f); } IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFont(NULL, style.FontSizeBase * factor) or use style.FontScaleMain to scale all windows. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 91d7e030d..f0927b899 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1334,7 +1334,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::SameLine(); HelpMarker( "ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, " "but the user can change it with a right-click on those inputs.\n\nColorPicker defaults to displaying RGB+HSV+Hex " - "if you don't specify a display mode.\n\nYou can change the defaults using SetColorEditOptions()."); + "if you don't specify a display mode.\n\nYou can change the defaults using style.ColorEditFlags."); ImGuiColorEditFlags flags = base_flags | color_picker_flags; if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; @@ -1347,14 +1347,14 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::Text("Set defaults in code:"); ImGui::SameLine(); HelpMarker( - "SetColorEditOptions() is designed to allow you to set boot-time default.\n" + "style.ColorEditFlags is designed to allow you to set boot-time default.\n" "We don't have Push/Pop functions because you can force options on a per-widget basis if needed, " "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid " "encouraging you to persistently save values that aren't forward-compatible."); - if (ImGui::Button("Default: Uint8 + HSV + Hue Bar")) - ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar); - if (ImGui::Button("Default: Float + HDR + Hue Wheel")) - ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel); + if (ImGui::Button("Overwrite default: Uint8 + HSV + Hue Bar")) + ImGui::GetStyle().ColorEditFlags = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar; + if (ImGui::Button("Overwrite default: Float + HDR + Hue Wheel")) + ImGui::GetStyle().ColorEditFlags = ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel; // Always display a small version of both types of pickers // (that's in order to make it more visible in the demo to people who are skimming quickly through it) diff --git a/imgui_internal.h b/imgui_internal.h index 129981aa5..7f5e90dc0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2528,7 +2528,6 @@ struct ImGuiContext ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; int BeginComboDepth; - ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets ImGuiID ColorEditCurrentID; // Set temporarily while inside of the parent-most ColorEdit4/ColorPicker4 (because they call each others). ImGuiID ColorEditSavedID; // ID we are saving/restoring HS for float ColorEditSavedHue; // Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ace655d4d..dec1f74b1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5751,7 +5751,6 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) // - RenderColorRectWithAlphaCheckerboard() [Internal] // - ColorPicker4() // - ColorButton() -// - SetColorEditOptions() // - ColorTooltip() [Internal] // - ColorEditOptionsPopup() [Internal] // - ColorPickerOptionsPopup() [Internal] @@ -5823,14 +5822,14 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Read stored options if (!(flags & ImGuiColorEditFlags_DisplayMask_)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_); + flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_); if (!(flags & ImGuiColorEditFlags_DataTypeMask_)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_); + flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_); if (!(flags & ImGuiColorEditFlags_PickerMask_)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_); + flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_PickerMask_); if (!(flags & ImGuiColorEditFlags_InputMask_)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_); - flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); + flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_InputMask_); + flags |= (g.Style.ColorEditFlags & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected @@ -6099,13 +6098,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl // Read stored options if (!(flags & ImGuiColorEditFlags_PickerMask_)) - flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; + flags |= ((g.Style.ColorEditFlags & ImGuiColorEditFlags_PickerMask_) ? g.Style.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; if (!(flags & ImGuiColorEditFlags_InputMask_)) - flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; + flags |= ((g.Style.ColorEditFlags & ImGuiColorEditFlags_InputMask_) ? g.Style.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected if (!(flags & ImGuiColorEditFlags_NoOptions)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_AlphaBar); // Setup int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; @@ -6525,25 +6524,18 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl return pressed; } -// Initialize/override default color options -// FIXME: Could be moved to a simple IO field. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// This allowed passing 0 to set default, whereas NewFrame() will assert if one of missing. Abide to old logic. void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; - if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0) - flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_; - if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) - flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; - if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) - flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; - if ((flags & ImGuiColorEditFlags_InputMask_) == 0) - flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected - g.ColorEditOptions = flags; + if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_; } + if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; } + if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; } + if ((flags & ImGuiColorEditFlags_InputMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; } + g.Style.ColorEditFlags = flags; } +#endif // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) @@ -6591,7 +6583,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) ImGuiContext& g = *GImGui; PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); - ImGuiColorEditFlags opts = g.ColorEditOptions; + ImGuiColorEditFlags opts = g.Style.ColorEditFlags; if (allow_opt_inputs) { if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB; @@ -6631,7 +6623,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) EndPopup(); } - g.ColorEditOptions = opts; + g.Style.ColorEditFlags = opts; PopItemFlag(); EndPopup(); } @@ -6659,7 +6651,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; ImVec2 backup_pos = GetCursorScreenPos(); if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup - g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); + g.Style.ColorEditFlags = (g.Style.ColorEditFlags & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); SetCursorScreenPos(backup_pos); ImVec4 previewing_ref_col; memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); @@ -6671,7 +6663,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (allow_opt_alpha_bar) { if (allow_opt_picker) Separator(); - CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + CheckboxFlags("Alpha Bar", &g.Style.ColorEditFlags, ImGuiColorEditFlags_AlphaBar); } PopItemFlag(); EndPopup(); From 9f7d8086fe653cf12b8c83961effd8e53a88d4ad Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 6 Jul 2026 16:02:17 +0200 Subject: [PATCH 099/254] (Breaking) ColorEdit: obsoleted SetColorEditOptions() function in favor of poking to io.ColorEditFlags. (Amend 0e479b9) Reordered IO fields breaks ABI backward compat which was never guaranteed. Occasional breaking is a good way to enforce that lack of guarantee! --- imgui.cpp | 21 ++++++++++++++------- imgui.h | 24 +++++++++++++----------- imgui_demo.cpp | 6 +++--- imgui_widgets.cpp | 26 +++++++++++++------------- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5f7ecef50..fbbaded6d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1548,7 +1548,6 @@ ImGuiStyle::ImGuiStyle() DragDropTargetPadding = 3.0f; // Size to expand the drag and drop target from actual target item size. ColorMarkerSize = 3.0f; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. - ColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. InputTextCursorSize = 1.0f; // Thickness of cursor/caret in InputText(). @@ -1671,8 +1670,8 @@ ImGuiIO::ImGuiIO() ConfigNavCursorVisibleAuto = true; ConfigNavCursorVisibleAlways = false; - // Miscellaneous options - MouseDrawCursor = false; + // Widget options + #ifdef __APPLE__ ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag #else @@ -1682,13 +1681,21 @@ ImGuiIO::ImGuiIO() ConfigInputTextCursorBlink = true; ConfigInputTextEnterKeepActive = false; ConfigDragClickToInputText = false; + ColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; ConfigWindowsCopyContentsWithCtrlC = false; ConfigScrollbarScrollByPage = true; + + // Ini Settings options ConfigIniSettingsSaveLastUsedDate = true; ConfigIniSettingsAutoDiscardMonths = 0; + ConfigDebugIniSettings = false; + + // Miscellaneous options + MouseDrawCursor = false; ConfigMemoryCompactTimer = 60.0f; + ConfigDebugIsDebuggerPresent = false; ConfigDebugHighlightIdConflicts = true; ConfigDebugHighlightIdConflictsShowItemPicker = true; @@ -11074,10 +11081,10 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f && "Invalid style setting!"); // Required otherwise cannot resize from borders. IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); - IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(g.Style.ColorEditFlags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes); IM_ASSERT(g.IO.MouseSingleClickDelay > g.IO.MouseDoubleClickTime); diff --git a/imgui.h b/imgui.h index e38d79b59..bb05b0365 100644 --- a/imgui.h +++ b/imgui.h @@ -2343,7 +2343,6 @@ struct ImGuiStyle float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size. float ColorMarkerSize; // Size of R/G/B/A color markers for ColorEdit4() and for Drags/Sliders when using ImGuiSliderFlags_ColorMarkers. ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. - ImGuiColorEditFlags ColorEditFlags; // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. float InputTextCursorSize; // Thickness of cursor/caret in InputText(). @@ -2437,7 +2436,19 @@ struct ImGuiIO bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. - // Ini Settings + // Widget options + bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. + bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. + bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). + bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). + ImGuiColorEditFlags ColorEditFlags; // // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. + bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. + bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] Ctrl+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. + bool ConfigScrollbarScrollByPage; // = true // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location. + + // Ini Settings options bool ConfigIniSettingsSaveLastUsedDate;// = true // Enable loading/saving last used day (YYYYMMDD) in some .ini struct, making things easier to audit and allowing custom tools to cleanup old data. int ConfigIniSettingsAutoDiscardMonths; // = 0 // [BETA] Set number of months after which unused .ini entries are discarded on load. Require platform_io.Platform_SessionDate to be set. For systems supporting the feature, .ini entries without a LastUsed field will always be discarded! Please report if you are using this. bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) @@ -2445,15 +2456,6 @@ struct ImGuiIO // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. - bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. - bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. - bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). - bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). - bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. - bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) - bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. - bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] Ctrl+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. - bool ConfigScrollbarScrollByPage; // = true // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location. float ConfigMemoryCompactTimer; // = 60.0f // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. // Inputs Behaviors diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f0927b899..d0d26fa9f 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1347,14 +1347,14 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::Text("Set defaults in code:"); ImGui::SameLine(); HelpMarker( - "style.ColorEditFlags is designed to allow you to set boot-time default.\n" + "io.ColorEditFlags is designed to allow you to set boot-time default.\n" "We don't have Push/Pop functions because you can force options on a per-widget basis if needed, " "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid " "encouraging you to persistently save values that aren't forward-compatible."); if (ImGui::Button("Overwrite default: Uint8 + HSV + Hue Bar")) - ImGui::GetStyle().ColorEditFlags = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar; + ImGui::GetIO().ColorEditFlags = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar; if (ImGui::Button("Overwrite default: Float + HDR + Hue Wheel")) - ImGui::GetStyle().ColorEditFlags = ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel; + ImGui::GetIO().ColorEditFlags = ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel; // Always display a small version of both types of pickers // (that's in order to make it more visible in the demo to people who are skimming quickly through it) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index dec1f74b1..aa21753de 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5822,14 +5822,14 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Read stored options if (!(flags & ImGuiColorEditFlags_DisplayMask_)) - flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_); + flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_); if (!(flags & ImGuiColorEditFlags_DataTypeMask_)) - flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_); + flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_); if (!(flags & ImGuiColorEditFlags_PickerMask_)) - flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_PickerMask_); + flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_PickerMask_); if (!(flags & ImGuiColorEditFlags_InputMask_)) - flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_InputMask_); - flags |= (g.Style.ColorEditFlags & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); + flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_InputMask_); + flags |= (g.IO.ColorEditFlags & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected @@ -6098,13 +6098,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl // Read stored options if (!(flags & ImGuiColorEditFlags_PickerMask_)) - flags |= ((g.Style.ColorEditFlags & ImGuiColorEditFlags_PickerMask_) ? g.Style.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; + flags |= ((g.IO.ColorEditFlags & ImGuiColorEditFlags_PickerMask_) ? g.IO.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; if (!(flags & ImGuiColorEditFlags_InputMask_)) - flags |= ((g.Style.ColorEditFlags & ImGuiColorEditFlags_InputMask_) ? g.Style.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; + flags |= ((g.IO.ColorEditFlags & ImGuiColorEditFlags_InputMask_) ? g.IO.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected if (!(flags & ImGuiColorEditFlags_NoOptions)) - flags |= (g.Style.ColorEditFlags & ImGuiColorEditFlags_AlphaBar); + flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_AlphaBar); // Setup int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; @@ -6533,7 +6533,7 @@ void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; } if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; } if ((flags & ImGuiColorEditFlags_InputMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; } - g.Style.ColorEditFlags = flags; + g.IO.ColorEditFlags = flags; } #endif @@ -6583,7 +6583,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) ImGuiContext& g = *GImGui; PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); - ImGuiColorEditFlags opts = g.Style.ColorEditFlags; + ImGuiColorEditFlags opts = g.IO.ColorEditFlags; if (allow_opt_inputs) { if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB; @@ -6623,7 +6623,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) EndPopup(); } - g.Style.ColorEditFlags = opts; + g.IO.ColorEditFlags = opts; PopItemFlag(); EndPopup(); } @@ -6651,7 +6651,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; ImVec2 backup_pos = GetCursorScreenPos(); if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup - g.Style.ColorEditFlags = (g.Style.ColorEditFlags & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); + g.IO.ColorEditFlags = (g.IO.ColorEditFlags & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); SetCursorScreenPos(backup_pos); ImVec4 previewing_ref_col; memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); @@ -6663,7 +6663,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (allow_opt_alpha_bar) { if (allow_opt_picker) Separator(); - CheckboxFlags("Alpha Bar", &g.Style.ColorEditFlags, ImGuiColorEditFlags_AlphaBar); + CheckboxFlags("Alpha Bar", &g.IO.ColorEditFlags, ImGuiColorEditFlags_AlphaBar); } PopItemFlag(); EndPopup(); From aca69173dfaf3c54c02a5f699ca4cd1211154446 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 16:00:01 +0200 Subject: [PATCH 100/254] (Breaking) ColorEdit: obsoleted SetColorEditOptions() function in favor of poking to io.ConfigColorEditFlags. (Amend 0e479b9, 9f7d808) Apologies, 9f7d808 was extremely flaky. --- docs/CHANGELOG.txt | 9 +++++---- imgui.cpp | 12 ++++++------ imgui.h | 6 +++--- imgui_demo.cpp | 8 ++++---- imgui_widgets.cpp | 26 +++++++++++++------------- 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c4de5cab9..6c4c9de19 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -44,7 +44,7 @@ Breaking Changes: - TreeNode: commented out legacy name `ImGuiTreeNodeFlags_SpanTextWidth` which was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in - of directly poking to style.ColorEditFlags. More consistent and easier to discover + of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. Other Changes: @@ -113,6 +113,9 @@ Other Changes: - Added io.MouseSingleClickDelay to configure default delayed single click delay when using GetItemClickedCountWithSingleClickDelay() or IsMouseReleasedWithDelay(). (#8337) Note that io.MouseSingleClickDelay is always > io.MouseDoubleClickTime. +- ColorEdit: + - Added `io.ConfigColorEditFlags` to read/modify current color edit/picker settings. + - ColorButton: small rendering tweak/optimization for the alpha checkerboard. - Style: - Added style.MenuItemRounding, ImGuiStyleVar_MenuItemRounding. (#7589, #9375, #9453) - Added style.SelectableRounding, ImGuiStyleVar_SelectableRounding. (#7589, #9375, #9453) @@ -134,8 +137,6 @@ Other Changes: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) - Added `ImDrawListFlags_TextNoPixelSnap` to disable snapping of AddText() coordinates for a given scope. (#3437, #9417, #2291) -- ColorButton: - - Small rendering tweak/optimization for the alpha checkerboard. - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). @@ -154,7 +155,7 @@ Other Changes: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends to spawn multiple-thread to manage OpenGL. (#9403) -- Examples: +- Examples: - Android: update to AGP 9.2.0 to support Gradle 9.6.0. - OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version string to ImGui_ImplOpenGL3_Init() to let backend decide of a GLSL version based diff --git a/imgui.cpp b/imgui.cpp index fbbaded6d..2e76a8e92 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,7 +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/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to style.ColorEditFlags. More consistent and easier to discover. + - 2026/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. - 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. - 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). - Before: void ImDrawList::AddRect(ImVec2 p_min, ImVec2 p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f); @@ -1681,7 +1681,7 @@ ImGuiIO::ImGuiIO() ConfigInputTextCursorBlink = true; ConfigInputTextEnterKeepActive = false; ConfigDragClickToInputText = false; - ColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. + ConfigColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; ConfigWindowsCopyContentsWithCtrlC = false; @@ -11081,10 +11081,10 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f && "Invalid style setting!"); // Required otherwise cannot resize from borders. IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); - IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo(g.IO.ColorEditFlags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_DataTypeMask_)); // " + IM_ASSERT(ImIsPowerOfTwo(g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_PickerMask_)); // " + IM_ASSERT(ImIsPowerOfTwo(g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_InputMask_)); // " IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes); IM_ASSERT(g.IO.MouseSingleClickDelay > g.IO.MouseDoubleClickTime); diff --git a/imgui.h b/imgui.h index bb05b0365..caae0458b 100644 --- a/imgui.h +++ b/imgui.h @@ -1917,9 +1917,9 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. - // Defaults Options copied to style.ColorEditFlags during initialization. + // Defaults Options copied to io.ConfigColorEditFlags during initialization. // The intent is that you probably don't want to override them in most of your calls. - // Let the user choose via the option menu and/or modify style.ColorEditFlags directly during startup if you want. + // Let the user choose via the option menu and/or modify io.ConfigColorEditFlags directly during startup if you want. ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, // [Internal] Masks @@ -2441,7 +2441,7 @@ struct ImGuiIO bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). - ImGuiColorEditFlags ColorEditFlags; // // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. + ImGuiColorEditFlags ConfigColorEditFlags; // // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d0d26fa9f..29822330d 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1334,7 +1334,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::SameLine(); HelpMarker( "ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, " "but the user can change it with a right-click on those inputs.\n\nColorPicker defaults to displaying RGB+HSV+Hex " - "if you don't specify a display mode.\n\nYou can change the defaults using style.ColorEditFlags."); + "if you don't specify a display mode.\n\nYou can change the defaults using io.ConfigColorEditFlags."); ImGuiColorEditFlags flags = base_flags | color_picker_flags; if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; @@ -1347,14 +1347,14 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::Text("Set defaults in code:"); ImGui::SameLine(); HelpMarker( - "io.ColorEditFlags is designed to allow you to set boot-time default.\n" + "io.ConfigColorEditFlags is designed to allow you to set boot-time default.\n" "We don't have Push/Pop functions because you can force options on a per-widget basis if needed, " "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid " "encouraging you to persistently save values that aren't forward-compatible."); if (ImGui::Button("Overwrite default: Uint8 + HSV + Hue Bar")) - ImGui::GetIO().ColorEditFlags = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar; + ImGui::GetIO().ConfigColorEditFlags = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar; if (ImGui::Button("Overwrite default: Float + HDR + Hue Wheel")) - ImGui::GetIO().ColorEditFlags = ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel; + ImGui::GetIO().ConfigColorEditFlags = ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel; // Always display a small version of both types of pickers // (that's in order to make it more visible in the demo to people who are skimming quickly through it) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index aa21753de..6276f7db4 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5822,14 +5822,14 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Read stored options if (!(flags & ImGuiColorEditFlags_DisplayMask_)) - flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_DisplayMask_); + flags |= (g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_DisplayMask_); if (!(flags & ImGuiColorEditFlags_DataTypeMask_)) - flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_DataTypeMask_); + flags |= (g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_DataTypeMask_); if (!(flags & ImGuiColorEditFlags_PickerMask_)) - flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_PickerMask_); + flags |= (g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_PickerMask_); if (!(flags & ImGuiColorEditFlags_InputMask_)) - flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_InputMask_); - flags |= (g.IO.ColorEditFlags & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); + flags |= (g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_InputMask_); + flags |= (g.IO.ConfigColorEditFlags & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected @@ -6098,13 +6098,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl // Read stored options if (!(flags & ImGuiColorEditFlags_PickerMask_)) - flags |= ((g.IO.ColorEditFlags & ImGuiColorEditFlags_PickerMask_) ? g.IO.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; + flags |= ((g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_PickerMask_) ? g.IO.ConfigColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; if (!(flags & ImGuiColorEditFlags_InputMask_)) - flags |= ((g.IO.ColorEditFlags & ImGuiColorEditFlags_InputMask_) ? g.IO.ColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; + flags |= ((g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_InputMask_) ? g.IO.ConfigColorEditFlags : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected if (!(flags & ImGuiColorEditFlags_NoOptions)) - flags |= (g.IO.ColorEditFlags & ImGuiColorEditFlags_AlphaBar); + flags |= (g.IO.ConfigColorEditFlags & ImGuiColorEditFlags_AlphaBar); // Setup int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; @@ -6533,7 +6533,7 @@ void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; } if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; } if ((flags & ImGuiColorEditFlags_InputMask_) == 0) { flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; } - g.IO.ColorEditFlags = flags; + g.IO.ConfigColorEditFlags = flags; } #endif @@ -6583,7 +6583,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) ImGuiContext& g = *GImGui; PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); - ImGuiColorEditFlags opts = g.IO.ColorEditFlags; + ImGuiColorEditFlags opts = g.IO.ConfigColorEditFlags; if (allow_opt_inputs) { if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB; @@ -6623,7 +6623,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) EndPopup(); } - g.IO.ColorEditFlags = opts; + g.IO.ConfigColorEditFlags = opts; PopItemFlag(); EndPopup(); } @@ -6651,7 +6651,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; ImVec2 backup_pos = GetCursorScreenPos(); if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup - g.IO.ColorEditFlags = (g.IO.ColorEditFlags & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); + g.IO.ConfigColorEditFlags = (g.IO.ConfigColorEditFlags & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); SetCursorScreenPos(backup_pos); ImVec4 previewing_ref_col; memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); @@ -6663,7 +6663,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (allow_opt_alpha_bar) { if (allow_opt_picker) Separator(); - CheckboxFlags("Alpha Bar", &g.IO.ColorEditFlags, ImGuiColorEditFlags_AlphaBar); + CheckboxFlags("Alpha Bar", &g.IO.ConfigColorEditFlags, ImGuiColorEditFlags_AlphaBar); } PopItemFlag(); EndPopup(); From 0785cdb2bb07e3c58e09c56ada0e3cbc74918a20 Mon Sep 17 00:00:00 2001 From: SeanTheBuilder1 Date: Tue, 31 Mar 2026 01:13:25 +0800 Subject: [PATCH 101/254] ColorPicker: added option to fix Saturation/Value triangle in wheel picker. (#9337) --- imgui.h | 5 +++-- imgui_widgets.cpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index caae0458b..c1a74f5c5 100644 --- a/imgui.h +++ b/imgui.h @@ -1914,8 +1914,9 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_Float = 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [Picker] // ColorPicker: bar for Hue, rectangle for Sat/Value. ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [Picker] // ColorPicker: wheel for Hue, triangle for Sat/Value. - ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. - ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. + ImGuiColorEditFlags_FixedTriangle = 1 << 27, // [Picker] // ColorPicker: fix triangle position for PickerHueWheel. + ImGuiColorEditFlags_InputRGB = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. + ImGuiColorEditFlags_InputHSV = 1 << 29, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. // Defaults Options copied to io.ConfigColorEditFlags during initialization. // The intent is that you probably don't want to override them in most of your calls. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6276f7db4..d4e39260d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6130,6 +6130,12 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + if (flags & ImGuiColorEditFlags_FixedTriangle) + { + triangle_pa = ImVec2(triangle_r * +0.866025f, triangle_r * +0.5f); // Hue point. + triangle_pb = ImVec2(0.0f, -triangle_r); // Black point. + triangle_pc = ImVec2(triangle_r * -0.866025f, triangle_r * +0.5f); // White point. + } float H = col[0], S = col[1], V = col[2]; float R = col[0], G = col[1], B = col[2]; @@ -6166,6 +6172,11 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); + if (flags & ImGuiColorEditFlags_FixedTriangle) + { + cos_hue_angle = ImCos(-0.0 * 2.0f * IM_PI); + sin_hue_angle = ImSin(-0.0 * 2.0f * IM_PI); + } if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) { // Interacting with SV triangle @@ -6374,6 +6385,11 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments); + if (flags & ImGuiColorEditFlags_FixedTriangle) + { + cos_hue_angle = ImCos(-0.0 * 2.0f * IM_PI); + sin_hue_angle = ImSin(-0.0 * 2.0f * IM_PI); + } // Render SV triangle (rotated according to hue) ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); From 5d56c0943243a75de5213c9e36653ff93ed0683a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Jun 2026 14:37:03 +0200 Subject: [PATCH 102/254] ColorPicker: added option to fix Saturation/Value triangle in wheel picker. Amends. (#9337) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 1 + imgui.h | 15 ++++++++------- imgui_demo.cpp | 1 + imgui_widgets.cpp | 28 +++++++++------------------- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6c4c9de19..4bceb3d99 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -115,6 +115,9 @@ Other Changes: Note that io.MouseSingleClickDelay is always > io.MouseDoubleClickTime. - ColorEdit: - Added `io.ConfigColorEditFlags` to read/modify current color edit/picker settings. + - ColorPicker: added `io.ConfigColorPickerRotateTriangle` to disable rotation of the + S/V triangle when in `ImGuiColorEditFlags_PickerHueWheel` mode. (#9337) [@SeanTheBuilder1] + Likely best passed to `style.ColorEditFlags` to setup globally and once. - ColorButton: small rendering tweak/optimization for the alpha checkerboard. - Style: - Added style.MenuItemRounding, ImGuiStyleVar_MenuItemRounding. (#7589, #9375, #9453) diff --git a/imgui.cpp b/imgui.cpp index 2e76a8e92..cf6ba2460 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1682,6 +1682,7 @@ ImGuiIO::ImGuiIO() ConfigInputTextEnterKeepActive = false; ConfigDragClickToInputText = false; ConfigColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. + ConfigColorPickerRotateTriangle = true; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; ConfigWindowsCopyContentsWithCtrlC = false; diff --git a/imgui.h b/imgui.h index c1a74f5c5..5f797aba3 100644 --- a/imgui.h +++ b/imgui.h @@ -243,7 +243,7 @@ typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: f typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: for BeginChild() -typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4() etc. +typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4(), style.ColorEditFlags etc. typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload() @@ -1904,7 +1904,8 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_AlphaNoBg = 1 << 13, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 14, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. - // User Options (right-click on widget to change some of them). + // User Options (right-click on widget to change some of them) + // Current settings are stored in style.ColorEditFlags. ImGuiColorEditFlags_AlphaBar = 1 << 18, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). ImGuiColorEditFlags_DisplayRGB = 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex. @@ -1914,9 +1915,8 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_Float = 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [Picker] // ColorPicker: bar for Hue, rectangle for Sat/Value. ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [Picker] // ColorPicker: wheel for Hue, triangle for Sat/Value. - ImGuiColorEditFlags_FixedTriangle = 1 << 27, // [Picker] // ColorPicker: fix triangle position for PickerHueWheel. - ImGuiColorEditFlags_InputRGB = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. - ImGuiColorEditFlags_InputHSV = 1 << 29, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. + ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. + ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. // Defaults Options copied to io.ConfigColorEditFlags during initialization. // The intent is that you probably don't want to override them in most of your calls. @@ -1933,8 +1933,8 @@ enum ImGuiColorEditFlags_ // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiColorEditFlags_AlphaPreview = 0, // Removed in 1.91.8. This is the default now. Will display a checkerboard unless ImGuiColorEditFlags_AlphaNoBg is set. -#endif //ImGuiColorEditFlags_RGB = ImGuiColorEditFlags_DisplayRGB, ImGuiColorEditFlags_HSV = ImGuiColorEditFlags_DisplayHSV, ImGuiColorEditFlags_HEX = ImGuiColorEditFlags_DisplayHex // [renamed in 1.69] +#endif }; // Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. @@ -2442,7 +2442,8 @@ struct ImGuiIO bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). - ImGuiColorEditFlags ConfigColorEditFlags; // // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. + ImGuiColorEditFlags ConfigColorEditFlags; // = // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. + bool ConfigColorPickerRotateTriangle;// = true // Rotate hue triangle in color picker. bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 29822330d..25cf384e5 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1326,6 +1326,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | base_flags); } } + ImGui::Checkbox("io.ConfigColorPickerRotate", &ImGui::GetIO().ConfigColorPickerRotateTriangle); ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0ImGuiColorEditFlags_PickerHueBar\0ImGuiColorEditFlags_PickerHueWheel\0"); ImGui::SameLine(); HelpMarker("When not specified explicitly, user can right-click the picker to change mode."); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d4e39260d..eedbb4400 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6126,16 +6126,11 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f); // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + const bool triangle_rotate = io.ConfigColorPickerRotateTriangle; float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); - ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. - ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. - ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. - if (flags & ImGuiColorEditFlags_FixedTriangle) - { - triangle_pa = ImVec2(triangle_r * +0.866025f, triangle_r * +0.5f); // Hue point. - triangle_pb = ImVec2(0.0f, -triangle_r); // Black point. - triangle_pc = ImVec2(triangle_r * -0.866025f, triangle_r * +0.5f); // White point. - } + ImVec2 triangle_pa = triangle_rotate ? ImVec2(triangle_r, 0.0f) : ImVec2(triangle_r * +0.866f, triangle_r * +0.5f); // Hue point. + ImVec2 triangle_pb = triangle_rotate ? ImVec2(triangle_r * -0.5f, triangle_r * -0.866f) : ImVec2(0.0f, -triangle_r); // Black point. + ImVec2 triangle_pc = triangle_rotate ? ImVec2(triangle_r * -0.5f, triangle_r * +0.866f) : ImVec2(triangle_r * -0.866f, triangle_r * +0.5f); // White point. float H = col[0], S = col[1], V = col[2]; float R = col[0], G = col[1], B = col[2]; @@ -6170,13 +6165,8 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl H += 1.0f; value_changed = value_changed_h = true; } - float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); - float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); - if (flags & ImGuiColorEditFlags_FixedTriangle) - { - cos_hue_angle = ImCos(-0.0 * 2.0f * IM_PI); - sin_hue_angle = ImSin(-0.0 * 2.0f * IM_PI); - } + float cos_hue_angle = triangle_rotate ? ImCos(-H * 2.0f * IM_PI) : 1.0f; + float sin_hue_angle = triangle_rotate ? ImSin(-H * 2.0f * IM_PI) : 0.0f; if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) { // Interacting with SV triangle @@ -6385,10 +6375,10 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments); - if (flags & ImGuiColorEditFlags_FixedTriangle) + if (triangle_rotate == false) { - cos_hue_angle = ImCos(-0.0 * 2.0f * IM_PI); - sin_hue_angle = ImSin(-0.0 * 2.0f * IM_PI); + cos_hue_angle = 1.0f; + sin_hue_angle = 0.0f; } // Render SV triangle (rotated according to hue) From f626c1c7933329ed74a442dd071fe2766ac774d4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 16:18:34 +0200 Subject: [PATCH 103/254] ColorPicker: added option to fix Saturation/Value triangle in wheel picker. Amends. (#9337) ConfigColorPickerRotateTriangle -> !ImGuiColorEditFlags_PickerNoRotate --- imgui.cpp | 1 - imgui.h | 8 ++++---- imgui_demo.cpp | 2 +- imgui_widgets.cpp | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index cf6ba2460..2e76a8e92 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1682,7 +1682,6 @@ ImGuiIO::ImGuiIO() ConfigInputTextEnterKeepActive = false; ConfigDragClickToInputText = false; ConfigColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. - ConfigColorPickerRotateTriangle = true; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; ConfigWindowsCopyContentsWithCtrlC = false; diff --git a/imgui.h b/imgui.h index 5f797aba3..d63544c12 100644 --- a/imgui.h +++ b/imgui.h @@ -243,7 +243,7 @@ typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: f typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: for BeginChild() -typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4(), style.ColorEditFlags etc. +typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4(), io.ConfigColorEditFlags etc. typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload() @@ -1915,8 +1915,9 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_Float = 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers. ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [Picker] // ColorPicker: bar for Hue, rectangle for Sat/Value. ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [Picker] // ColorPicker: wheel for Hue, triangle for Sat/Value. - ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. - ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. + ImGuiColorEditFlags_PickerNoRotate = 1 << 27, // [Picker] // ColorPicker: disable rotating Sat/Value triangle. Best set in io.ConfigColorEditFlags once. + ImGuiColorEditFlags_InputRGB = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. + ImGuiColorEditFlags_InputHSV = 1 << 29, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. // Defaults Options copied to io.ConfigColorEditFlags during initialization. // The intent is that you probably don't want to override them in most of your calls. @@ -2443,7 +2444,6 @@ struct ImGuiIO bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). ImGuiColorEditFlags ConfigColorEditFlags; // = // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. - bool ConfigColorPickerRotateTriangle;// = true // Rotate hue triangle in color picker. bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 25cf384e5..b1a5d7498 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1326,7 +1326,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | base_flags); } } - ImGui::Checkbox("io.ConfigColorPickerRotate", &ImGui::GetIO().ConfigColorPickerRotateTriangle); + ImGui::CheckboxFlags("ImGuiColorEditFlags_PickerNoRotate", &color_picker_flags, ImGuiColorEditFlags_PickerNoRotate); ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0ImGuiColorEditFlags_PickerHueBar\0ImGuiColorEditFlags_PickerHueWheel\0"); ImGui::SameLine(); HelpMarker("When not specified explicitly, user can right-click the picker to change mode."); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index eedbb4400..c787a0bbd 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6126,7 +6126,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f); // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. - const bool triangle_rotate = io.ConfigColorPickerRotateTriangle; + const bool triangle_rotate = (flags & ImGuiColorEditFlags_PickerNoRotate) == 0; float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); ImVec2 triangle_pa = triangle_rotate ? ImVec2(triangle_r, 0.0f) : ImVec2(triangle_r * +0.866f, triangle_r * +0.5f); // Hue point. ImVec2 triangle_pb = triangle_rotate ? ImVec2(triangle_r * -0.5f, triangle_r * -0.866f) : ImVec2(0.0f, -triangle_r); // Black point. From cfdc953ba91fa6dc093dfd3223977fe9d8e00a9c Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 16:21:36 +0200 Subject: [PATCH 104/254] ColorPicker: added option to fix Saturation/Value triangle in wheel picker. Amends. (#9337) The heat makes this week really not good with human mistakes I guess. --- docs/CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4bceb3d99..445432bb1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -115,7 +115,7 @@ Other Changes: Note that io.MouseSingleClickDelay is always > io.MouseDoubleClickTime. - ColorEdit: - Added `io.ConfigColorEditFlags` to read/modify current color edit/picker settings. - - ColorPicker: added `io.ConfigColorPickerRotateTriangle` to disable rotation of the + - ColorPicker: added `ImGuiColorEditFlags_PickerNoRotate` to disable rotation of the S/V triangle when in `ImGuiColorEditFlags_PickerHueWheel` mode. (#9337) [@SeanTheBuilder1] Likely best passed to `style.ColorEditFlags` to setup globally and once. - ColorButton: small rendering tweak/optimization for the alpha checkerboard. From 520a29dd518be4f3bdfc4a4740660b491ecfd3bb Mon Sep 17 00:00:00 2001 From: Kian <92978255+MERL10N@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:34:32 -0700 Subject: [PATCH 105/254] Backends: Metal4: added metal-cpp support. (#9461) with amends. --- backends/imgui_impl_metal.mm | 1 - backends/imgui_impl_metal4.h | 33 +++++++++++++++++++++++++++++++- backends/imgui_impl_metal4.mm | 36 ++++++++++++++++++++++++++++++++++- docs/CHANGELOG.txt | 2 +- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 652668c78..445c4b36c 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -123,7 +123,6 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, ImGui_ImplMetal_RenderDrawData(draw_data, (__bridge id)(commandBuffer), (__bridge id)(commandEncoder)); - } bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) diff --git a/backends/imgui_impl_metal4.h b/backends/imgui_impl_metal4.h index b5ec4ca56..b5b54cad0 100644 --- a/backends/imgui_impl_metal4.h +++ b/backends/imgui_impl_metal4.h @@ -7,7 +7,6 @@ // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // Missing features or Issues: -// [ ] Metal-cpp support. // [ ] Texture view pool support? Reevaluate which type to use for ImtextureID. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -53,6 +52,38 @@ IMGUI_IMPL_API void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex); #endif +//----------------------------------------------------------------------------- +// C++ API +//----------------------------------------------------------------------------- + +// Enable Metal C++ binding support with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file +// More info about using Metal from C++: https://developer.apple.com/metal/cpp/ + +#ifdef IMGUI_IMPL_METAL_CPP +#ifndef __OBJC__ + +// Forward declare relevant classes from the MTL and MTL4 namespace +namespace MTL { class Device; } +namespace MTL4 { class CommandQueue; class CommandBuffer; class RenderPassDescriptor; class RenderCommandEncoder; } + +// Follow "Getting Started" link and check examples/ folder to learn about using backends! +IMGUI_IMPL_API bool ImGui_ImplMetal4_Init(MTL::Device* device, MTL4::CommandQueue* commandQueue, int framesInFlight); +IMGUI_IMPL_API void ImGui_ImplMetal4_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplMetal4_NewFrame(MTL4::RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex); +IMGUI_IMPL_API void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, + MTL4::CommandBuffer* commandBuffer, + MTL4::RenderCommandEncoder* commandEncoder); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplMetal4_CreateDeviceObjects(MTL::Device* device); +IMGUI_IMPL_API void ImGui_ImplMetal4_DestroyDeviceObjects(); + +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually. +IMGUI_IMPL_API void ImGui_ImplMetal4_UpdateTexture(ImTextureData* tex); + +#endif +#endif + //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index f88d34b4e..d2d172a57 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -7,7 +7,6 @@ // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // Missing features or Issues: -// [ ] Metal-cpp support. // [ ] Texture view pool support? Reevaluate which type to use for ImtextureID. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. @@ -20,6 +19,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-07: Metal 4: Added metal-cpp support. (#9461) // 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) #include "imgui.h" @@ -28,6 +28,10 @@ #import #import +#ifdef IMGUI_IMPL_METAL_CPP +#include +#endif + #pragma mark - Support classes and structs struct ImGui_Metal4_ConstantData @@ -95,6 +99,36 @@ static void ImGui_ImplMetal4_DestroyBackendData(){ IM_DELET static inline CFTimeInterval GetMachAbsoluteTimeInSeconds() { return (CFTimeInterval)(double)(clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1e9); } +#ifdef IMGUI_IMPL_METAL_CPP + +#pragma mark - Dear ImGui Metal C++ Backend API + +bool ImGui_ImplMetal4_Init(MTL::Device* device, MTL4::CommandQueue* commandQueue, int framesInFlight) +{ + return ImGui_ImplMetal4_Init((__bridge id)(device),(__bridge id)(commandQueue), framesInFlight); +} + +void ImGui_ImplMetal4_NewFrame(MTL4::RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex) +{ + ImGui_ImplMetal4_NewFrame((__bridge MTL4RenderPassDescriptor*)(renderPassDescriptor), frameInFlightIndex); +} + +void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, + MTL4::CommandBuffer* commandBuffer, + MTL4::RenderCommandEncoder* commandEncoder) +{ + ImGui_ImplMetal4_RenderDrawData(draw_data, + (__bridge id)(commandBuffer), + (__bridge id)(commandEncoder)); +} + +bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) +{ + return ImGui_ImplMetal4_CreateDeviceObjects((__bridge id)(device)); +} + +#endif // #ifdef IMGUI_IMPL_METAL_CPP + #pragma mark - Dear ImGui Metal Backend API void ImGui_ImplMetal4_NewFrame(MTL4RenderPassDescriptor* renderPassDescriptor, int frameInFlightIndex) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 445432bb1..b0afa23f8 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -148,7 +148,7 @@ Other Changes: - Backends: - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Note that Metal-cpp is not yet supported. + - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] From 481f2cea17e5862c2f27934c6cb8d4ef1c134191 Mon Sep 17 00:00:00 2001 From: Kian <92978255+MERL10N@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:13:19 -0700 Subject: [PATCH 106/254] Backends: Metal4: added ImTextureID tex_id to residency set. (#9461, #9458, #9451) This will resolve runtime errors identified by XCode's Metal Shader Validator. --- backends/imgui_impl_metal4.mm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index d2d172a57..2866ed1e2 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -239,9 +239,6 @@ void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, idDisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) - // Before rendering command lists, commit residency set - [bd->SharedMetalContext.residencySet commit]; - // Render command lists size_t vertexBufferOffset = 0; size_t indexBufferOffset = 0; @@ -292,6 +289,7 @@ void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, id texture = (__bridge id)(void*)(intptr_t)tex_id; + [bd->SharedMetalContext.residencySet addAllocation:texture]; [bd->SharedMetalContext.argumentTable setTexture:texture.gpuResourceID atIndex:0]; } @@ -317,6 +315,9 @@ void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, idSharedMetalContext.residencySet commit]; bd->RenderCommandEncoder = nil; } From d1eb8d031c69689371e72b027a472d12a0d9a14e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 22:00:26 +0200 Subject: [PATCH 107/254] Settings: added explicit DiscardWhenMissingDate. (#9460, #9108) --- imgui_internal.h | 3 ++- imgui_tables.cpp | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 7f5e90dc0..c9ee4cbba 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2048,7 +2048,8 @@ struct ImGuiWindowSettings struct ImGuiSettingsCleanupArgs { - int DiscardOlderThanMonths = 0; // Enable to discard entries older than XX months. + int DiscardOlderThanMonths = 0; // Enable to discard entries older than XX months. + bool DiscardWhenMissingDate = false; // Enable to discard entries missing a date. bool SetCurrentSessionDateToAll = false; // Enable to write current SessionDate to all supporting entries. // Let us know in #9460 if you use this. bool SetCurrentSessionDateWhenMissingDate = false; // Enable to write current SessionDate to all supporting entries missing a date. // Let us know in #9460 if you use this. int _DiscardOlderThanDate = 0; // [Internal] diff --git a/imgui_tables.cpp b/imgui_tables.cpp index aa0b89efe..2fcce6cca 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4134,9 +4134,12 @@ static void TableSettingsHandler_Cleanup(ImGuiContext* ctx, ImGuiSettingsHandler table->SettingsOffset = -1; for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) { + const bool is_valid = settings->LastUsedDate.IsValid(); if (args->_DiscardOlderThanDate != 0 && settings->LastUsedDate.Unpack() < args->_DiscardOlderThanDate) settings->ID = 0; - if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && settings->LastUsedDate.IsValid() == false)) + else if (args->DiscardWhenMissingDate && !is_valid) + settings->ID = 0; + else if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && !is_valid)) settings->LastUsedDate = g.SessionDate; } } From 85593d0869ecc9c49c9b8aa83c0433275e982e67 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 22:06:43 +0200 Subject: [PATCH 108/254] Tables, Settings: make TableSettingsCreate() recycle existing unbound entry (might happen after a GC or anything clearing table->SettingsOffset. (#9460, #9108) --- imgui_tables.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2fcce6cca..e1328fe59 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3875,8 +3875,15 @@ static size_t TableSettingsCalcChunkSize(int columns_count) ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) { ImGuiContext& g = *GImGui; - //ImGuiTableSettings* old_settings = TableSettingsFindByID(id); // Comment out sanity check to avoid unnecessary lookups. - //IM_ASSERT(old_settings == NULL || old_settings->ColumnsCountMax < columns_count); + if (ImGuiTableSettings* settings = TableSettingsFindByID(id)) + { + if (settings->ColumnsCountMax >= columns_count) + { + TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle + return settings; + } + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count)); TableSettingsInit(settings, id, columns_count, columns_count); return settings; @@ -4162,16 +4169,6 @@ static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, int columns_count = 0; if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) return NULL; - - if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id)) - { - if (settings->ColumnsCountMax >= columns_count) - { - TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle - return settings; - } - settings->ID = 0; // Invalidate storage, we won't fit because of a count change - } return ImGui::TableSettingsCreate(id, columns_count); } From 2c49da44be5d878e69968bc4032f6c5e6c6dbade Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 22:17:55 +0200 Subject: [PATCH 109/254] Settings: added a type name filter to CleanupIniSettings() / ImGuiSettingsCleanupArgs. (#9460) --- imgui.cpp | 6 ++++-- imgui_internal.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2e76a8e92..3a43223e1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15713,12 +15713,14 @@ void ImGui::CleanupIniSettings(ImGuiSettingsCleanupArgs* args) ImGuiContext& g = *GImGui; if (g.PlatformIO.Platform_SessionDate == 0) return; + ImGuiID type_hash_filter = args->TypeName ? ImHashStr(args->TypeName) : 0; ImGuiPackedDate discard_older_than_date_p = g.PlatformIO.Platform_SessionDate; discard_older_than_date_p.SubtractMonths(args->DiscardOlderThanMonths); args->_DiscardOlderThanDate = discard_older_than_date_p.Unpack(); for (ImGuiSettingsHandler& handler : g.SettingsHandlers) - if (handler.CleanupFn != NULL) - handler.CleanupFn(&g, &handler, args); + if (type_hash_filter == 0 || handler.TypeHash == type_hash_filter) + if (handler.CleanupFn != NULL) + handler.CleanupFn(&g, &handler, args); } void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) diff --git a/imgui_internal.h b/imgui_internal.h index c9ee4cbba..33f3ad756 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2048,6 +2048,7 @@ struct ImGuiWindowSettings struct ImGuiSettingsCleanupArgs { + const char* TypeName = NULL; // Set to restrict cleanup to a given .ini type, e.g. "Window", "Table". Otherwise every types supporting Cleanup are supported. int DiscardOlderThanMonths = 0; // Enable to discard entries older than XX months. bool DiscardWhenMissingDate = false; // Enable to discard entries missing a date. bool SetCurrentSessionDateToAll = false; // Enable to write current SessionDate to all supporting entries. // Let us know in #9460 if you use this. From 112d6d9eabe3995e0cfa0a093e287c68f865d3f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 22:30:15 +0200 Subject: [PATCH 110/254] Settings: added a type name filter to CleanupIniSettings() / ImGuiSettingsCleanupArgs. Amends. (#9460) Request hash because storing const char* strings in struct is not entirely zen. Amend 2c49da4. --- imgui.cpp | 3 +-- imgui_internal.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 3a43223e1..43409848e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15713,12 +15713,11 @@ void ImGui::CleanupIniSettings(ImGuiSettingsCleanupArgs* args) ImGuiContext& g = *GImGui; if (g.PlatformIO.Platform_SessionDate == 0) return; - ImGuiID type_hash_filter = args->TypeName ? ImHashStr(args->TypeName) : 0; ImGuiPackedDate discard_older_than_date_p = g.PlatformIO.Platform_SessionDate; discard_older_than_date_p.SubtractMonths(args->DiscardOlderThanMonths); args->_DiscardOlderThanDate = discard_older_than_date_p.Unpack(); for (ImGuiSettingsHandler& handler : g.SettingsHandlers) - if (type_hash_filter == 0 || handler.TypeHash == type_hash_filter) + if (args->TypeHashFilter == 0 || handler.TypeHash == args->TypeHashFilter) if (handler.CleanupFn != NULL) handler.CleanupFn(&g, &handler, args); } diff --git a/imgui_internal.h b/imgui_internal.h index 33f3ad756..a4be1621a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2048,7 +2048,7 @@ struct ImGuiWindowSettings struct ImGuiSettingsCleanupArgs { - const char* TypeName = NULL; // Set to restrict cleanup to a given .ini type, e.g. "Window", "Table". Otherwise every types supporting Cleanup are supported. + ImGuiID TypeHashFilter = 0; // Set to restrict cleanup to a given .ini type, e.g. == ImHashStr("Window"), ImHashStr("Table"). Otherwise every types supporting Cleanup will be affected. int DiscardOlderThanMonths = 0; // Enable to discard entries older than XX months. bool DiscardWhenMissingDate = false; // Enable to discard entries missing a date. bool SetCurrentSessionDateToAll = false; // Enable to write current SessionDate to all supporting entries. // Let us know in #9460 if you use this. From f903a7e8e23f2d80fbb5198e9e049632a28d798f Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2026 22:52:47 +0200 Subject: [PATCH 111/254] Settings: fixed Windows not honoring DiscardWhenMissingDate. (#9460) --- imgui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 43409848e..5efb3a6e2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15904,9 +15904,10 @@ static void WindowSettingsHandler_Cleanup(ImGuiContext* ctx, ImGuiSettingsHandle ImGuiContext& g = *ctx; for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) { - if (args->_DiscardOlderThanDate != 0 && settings->LastUsedDate.Unpack() < args->_DiscardOlderThanDate) + const bool is_valid = settings->LastUsedDate.IsValid(); + if ((args->_DiscardOlderThanDate != 0 && settings->LastUsedDate.Unpack() < args->_DiscardOlderThanDate) || (args->DiscardWhenMissingDate && !is_valid)) settings->WantDelete = true; - if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && settings->LastUsedDate.IsValid() == false)) + if (args->SetCurrentSessionDateToAll || (args->SetCurrentSessionDateWhenMissingDate && !is_valid)) settings->LastUsedDate = g.SessionDate; } } From 68fce9f3bddb6c3e015a33150796847190f50ac6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 8 Jul 2026 15:02:56 +0200 Subject: [PATCH 112/254] Settings: added DiscardAll option to CleanupIniSettings() / ImGuiSettingsCleanupArgs. (#9460) + Demo: tweak for scaling. --- imgui.cpp | 16 +++++++++++----- imgui_demo.cpp | 5 +++-- imgui_internal.h | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5efb3a6e2..2954bd6eb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15711,11 +15711,17 @@ void ImGui::ClearIniSettings() void ImGui::CleanupIniSettings(ImGuiSettingsCleanupArgs* args) { ImGuiContext& g = *GImGui; - if (g.PlatformIO.Platform_SessionDate == 0) - return; - ImGuiPackedDate discard_older_than_date_p = g.PlatformIO.Platform_SessionDate; - discard_older_than_date_p.SubtractMonths(args->DiscardOlderThanMonths); - args->_DiscardOlderThanDate = discard_older_than_date_p.Unpack(); + if (args->DiscardAll) + for (ImGuiSettingsHandler& handler : g.SettingsHandlers) + if (args->TypeHashFilter == 0 || handler.TypeHash == args->TypeHashFilter) + if (handler.ClearAllFn != NULL) + handler.ClearAllFn(&g, &handler); + if (g.PlatformIO.Platform_SessionDate != 0 && args->DiscardOlderThanMonths != 0) + { + ImGuiPackedDate discard_older_than_date_p = g.PlatformIO.Platform_SessionDate; + discard_older_than_date_p.SubtractMonths(args->DiscardOlderThanMonths); + args->_DiscardOlderThanDate = discard_older_than_date_p.Unpack(); + } for (ImGuiSettingsHandler& handler : g.SettingsHandlers) if (args->TypeHashFilter == 0 || handler.TypeHash == args->TypeHashFilter) if (handler.CleanupFn != NULL) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b1a5d7498..b1a326570 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -5092,7 +5092,8 @@ static void DemoWindowLayout() // If you want to create your own time line for a real application you may be better off manipulating // the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets // yourself. You may also want to use the lower-level ImDrawList API. - int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + const int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + const float base_w = ImGui::GetFontSize() * 3; for (int n = 0; n < num_buttons; n++) { if (n > 0) ImGui::SameLine(); @@ -5104,7 +5105,7 @@ static void DemoWindowLayout() ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); - ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::Button(label, ImVec2(base_w + sinf((float)(line + n)) * base_w * 0.5f, 0.0f)); ImGui::PopStyleColor(3); ImGui::PopID(); } diff --git a/imgui_internal.h b/imgui_internal.h index a4be1621a..23b8ec0fd 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2051,6 +2051,7 @@ struct ImGuiSettingsCleanupArgs ImGuiID TypeHashFilter = 0; // Set to restrict cleanup to a given .ini type, e.g. == ImHashStr("Window"), ImHashStr("Table"). Otherwise every types supporting Cleanup will be affected. int DiscardOlderThanMonths = 0; // Enable to discard entries older than XX months. bool DiscardWhenMissingDate = false; // Enable to discard entries missing a date. + bool DiscardAll = false; // Enable to discard all entries = same as calling ClearIniSettings() except it may be filtered. bool SetCurrentSessionDateToAll = false; // Enable to write current SessionDate to all supporting entries. // Let us know in #9460 if you use this. bool SetCurrentSessionDateWhenMissingDate = false; // Enable to write current SessionDate to all supporting entries missing a date. // Let us know in #9460 if you use this. int _DiscardOlderThanDate = 0; // [Internal] From 62399b5bfec49a5eab808be1a41c2d018d240777 Mon Sep 17 00:00:00 2001 From: Jonathan Hoffstadt Date: Wed, 8 Jul 2026 10:14:55 -0500 Subject: [PATCH 113/254] Examples: Apple + Metal4: add example (#9466, #9451) + Amend edit to EXAMPLES.md, comment build.xml --- .github/workflows/build.yml | 4 + docs/CHANGELOG.txt | 1 + docs/EXAMPLES.md | 8 +- examples/example_apple_metal4/Makefile | 21 + examples/example_apple_metal4/README.md | 10 + .../project.pbxproj | 535 ++++++++++++++++++ .../example_apple_metal4/iOS/Info-iOS.plist | 49 ++ .../iOS/LaunchScreen.storyboard | 27 + .../macOS/Info-macOS.plist | 30 + .../macOS/MainMenu.storyboard | 93 +++ examples/example_apple_metal4/main.mm | 366 ++++++++++++ 11 files changed, 1141 insertions(+), 3 deletions(-) create mode 100644 examples/example_apple_metal4/Makefile create mode 100644 examples/example_apple_metal4/README.md create mode 100644 examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj create mode 100644 examples/example_apple_metal4/iOS/Info-iOS.plist create mode 100644 examples/example_apple_metal4/iOS/LaunchScreen.storyboard create mode 100644 examples/example_apple_metal4/macOS/Info-macOS.plist create mode 100644 examples/example_apple_metal4/macOS/MainMenu.storyboard create mode 100644 examples/example_apple_metal4/main.mm diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3980838a4..a7e1095c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -593,6 +593,10 @@ jobs: run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos if: github.event_name == 'workflow_run' + #- name: Build macOS example_apple_metal4 + # run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos + # if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_opengl2 run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 if: github.event_name == 'workflow_run' diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index b0afa23f8..373da111f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -160,6 +160,7 @@ Other Changes: to spawn multiple-thread to manage OpenGL. (#9403) - Examples: - Android: update to AGP 9.2.0 to support Gradle 9.6.0. + - Apple+Metal4: added new example. (#9465, #9451) [@hoffstadt] - OpenGL3+GLFW/SDL2/SDL3: allow Wine compatibility by passing empty GLSL version string to ImGui_ImplOpenGL3_Init() to let backend decide of a GLSL version based on actual GL version obtained. (#9427, #6577) [@perminovVS] diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 3d410477b..6315a3a5c 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -60,10 +60,12 @@ Android + OpenGL3 (ES) example.
[example_apple_metal/](https://github.com/ocornut/imgui/tree/master/examples/example_apple_metal/)
OSX & iOS + Metal example.
-= main.m + imgui_impl_osx.mm + imgui_impl_metal.mm
+= main.mm + imgui_impl_osx.mm + imgui_impl_metal.mm
It is based on the "cross-platform" game template provided with Xcode as of Xcode 9. -(NB: imgui_impl_osx.mm is currently not as feature complete as other platforms backends. -You may prefer to use the GLFW Or SDL backends, which will also support Windows and Linux.) + +[example_apple_metal4/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_metal4/)
+OSX & iOS + Metal4 example, Mac only.
+= main.mm + imgui_impl_osx.mm + imgui_impl_metal4.mm
[example_apple_opengl2/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_opengl2/)
OSX + OpenGL2 example.
diff --git a/examples/example_apple_metal4/Makefile b/examples/example_apple_metal4/Makefile new file mode 100644 index 000000000..35cdac769 --- /dev/null +++ b/examples/example_apple_metal4/Makefile @@ -0,0 +1,21 @@ +# Makefile for example_apple_metal4, for macOS only (**not iOS**) +CXX = clang++ +EXE = example_apple_metal4 +IMGUI_DIR = ../../ +SOURCES = main.mm +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_osx.mm $(IMGUI_DIR)/backends/imgui_impl_metal4.mm + +CXXFLAGS = -std=c++11 -ObjC++ -fobjc-arc -Wall -Wextra -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +FRAMEWORKS = -framework AppKit -framework Metal -framework MetalKit -framework QuartzCore -framework GameController + +all: $(EXE) + +$(EXE): $(SOURCES) + $(CXX) $(CXXFLAGS) $^ $(FRAMEWORKS) -o $@ + +run: all + ./$(EXE) + +clean: + rm -f $(EXE) *.o diff --git a/examples/example_apple_metal4/README.md b/examples/example_apple_metal4/README.md new file mode 100644 index 000000000..240ae0312 --- /dev/null +++ b/examples/example_apple_metal4/README.md @@ -0,0 +1,10 @@ +# iOS / OSX Metal4 example + +## Introduction + +This example shows how to integrate Dear ImGui with Metal4. It is based on the "cross-platform" game template provided with Xcode as of Xcode 9. + +Consider basing your work off the example_glfw_metal/ or example_sdl2_metal/ examples. They are better supported and will be portable unlike this one. + + + diff --git a/examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj b/examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ed60054fd --- /dev/null +++ b/examples/example_apple_metal4/example_apple_metal4.xcodeproj/project.pbxproj @@ -0,0 +1,535 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 050450AB2768052600AB6805 /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; }; + 050450AD276863B000AB6805 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 050450AC276863B000AB6805 /* QuartzCore.framework */; }; + 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05318E0E274C397200A8DE2E /* GameController.framework */; }; + 05A275442773BEA20084EF39 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05A275432773BEA20084EF39 /* QuartzCore.framework */; }; + 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; }; + 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; }; + 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; }; + 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */; }; + 8309BDA5253CCC070045E2A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDA0253CCBC10045E2A1 /* main.mm */; }; + 8309BDA8253CCC080045E2A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDA0253CCBC10045E2A1 /* main.mm */; }; + 8309BDBB253CCCAD0045E2A1 /* imgui_impl_metal4.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */; }; + 8309BDBE253CCCB60045E2A1 /* imgui_impl_metal4.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */; }; + 8309BDBF253CCCB60045E2A1 /* imgui_impl_osx.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */; }; + 8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */; }; + 8309BDFC253CDAB30045E2A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8309BDF7253CDAAE0045E2A1 /* LaunchScreen.storyboard */; }; + 8309BE04253CDAB60045E2A1 /* MainMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8309BDFA253CDAAE0045E2A1 /* MainMenu.storyboard */; }; + 83BBE9E520EB46B900295997 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9E420EB46B900295997 /* Metal.framework */; }; + 83BBE9E720EB46BD00295997 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9E620EB46BD00295997 /* MetalKit.framework */; }; + 83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9EA20EB471700295997 /* MetalKit.framework */; }; + 83BBE9ED20EB471700295997 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83BBE9EB20EB471700295997 /* Metal.framework */; }; + 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0120EB54E700295997 /* imgui_draw.cpp */; }; + 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0120EB54E700295997 /* imgui_draw.cpp */; }; + 83BBEA0720EB54E700295997 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0220EB54E700295997 /* imgui_demo.cpp */; }; + 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0220EB54E700295997 /* imgui_demo.cpp */; }; + 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0320EB54E700295997 /* imgui.cpp */; }; + 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BBEA0320EB54E700295997 /* imgui.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 050450AC276863B000AB6805 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 05318E0E274C397200A8DE2E /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; + 05A2754027728F5B0084EF39 /* imgui_impl_metal4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_metal4.h; path = ../../backends/imgui_impl_metal4.h; sourceTree = ""; }; + 05A2754127728F5B0084EF39 /* imgui_impl_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_osx.h; path = ../../backends/imgui_impl_osx.h; sourceTree = ""; }; + 05A275432773BEA20084EF39 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.2.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + 07A82ED62139413C0078D120 /* imgui_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_internal.h; path = ../../imgui_internal.h; sourceTree = ""; }; + 07A82ED72139413C0078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = ""; }; + 5079822D257677DB0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = ""; }; + 8307E7C420E9F9C900473790 /* example_apple_metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_apple_metal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8307E7DA20E9F9C900473790 /* example_apple_metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_apple_metal.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 8309BDA0253CCBC10045E2A1 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = imgui_impl_metal4.mm; path = ../../backends/imgui_impl_metal4.mm; sourceTree = ""; }; + 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = imgui_impl_osx.mm; path = ../../backends/imgui_impl_osx.mm; sourceTree = ""; }; + 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 8309BDF7253CDAAE0045E2A1 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 8309BDF8253CDAAE0045E2A1 /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; + 8309BDFA253CDAAE0045E2A1 /* MainMenu.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainMenu.storyboard; sourceTree = ""; }; + 8309BDFB253CDAAE0045E2A1 /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-macOS.plist"; sourceTree = ""; }; + 83BBE9E420EB46B900295997 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/Metal.framework; sourceTree = DEVELOPER_DIR; }; + 83BBE9E620EB46BD00295997 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/MetalKit.framework; sourceTree = DEVELOPER_DIR; }; + 83BBE9E820EB46C100295997 /* ModelIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ModelIO.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/Frameworks/ModelIO.framework; sourceTree = DEVELOPER_DIR; }; + 83BBE9EA20EB471700295997 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 83BBE9EB20EB471700295997 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 83BBE9EE20EB471C00295997 /* ModelIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ModelIO.framework; path = System/Library/Frameworks/ModelIO.framework; sourceTree = SDKROOT; }; + 83BBEA0020EB54E700295997 /* imgui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui.h; path = ../../imgui.h; sourceTree = ""; }; + 83BBEA0120EB54E700295997 /* imgui_draw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_draw.cpp; path = ../../imgui_draw.cpp; sourceTree = ""; }; + 83BBEA0220EB54E700295997 /* imgui_demo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_demo.cpp; path = ../../imgui_demo.cpp; sourceTree = ""; }; + 83BBEA0320EB54E700295997 /* imgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../../imgui.cpp; sourceTree = ""; }; + 83BBEA0420EB54E700295997 /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8307E7C120E9F9C900473790 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 05A275442773BEA20084EF39 /* QuartzCore.framework in Frameworks */, + 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */, + 83BBE9E720EB46BD00295997 /* MetalKit.framework in Frameworks */, + 83BBE9E520EB46B900295997 /* Metal.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8307E7D720E9F9C900473790 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 050450AD276863B000AB6805 /* QuartzCore.framework in Frameworks */, + 8309BDC6253CCCFE0045E2A1 /* AppKit.framework in Frameworks */, + 83BBE9EC20EB471700295997 /* MetalKit.framework in Frameworks */, + 05318E0F274C397200A8DE2E /* GameController.framework in Frameworks */, + 83BBE9ED20EB471700295997 /* Metal.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8307E7B520E9F9C700473790 = { + isa = PBXGroup; + children = ( + 83BBE9F020EB544400295997 /* imgui */, + 8309BD9E253CCBA70045E2A1 /* example */, + 8307E7C520E9F9C900473790 /* Products */, + 83BBE9E320EB46B800295997 /* Frameworks */, + ); + sourceTree = ""; + }; + 8307E7C520E9F9C900473790 /* Products */ = { + isa = PBXGroup; + children = ( + 8307E7C420E9F9C900473790 /* example_apple_metal.app */, + 8307E7DA20E9F9C900473790 /* example_apple_metal.app */, + ); + name = Products; + sourceTree = ""; + }; + 8309BD9E253CCBA70045E2A1 /* example */ = { + isa = PBXGroup; + children = ( + 8309BDF6253CDAAE0045E2A1 /* iOS */, + 8309BDF9253CDAAE0045E2A1 /* macOS */, + 8309BDA0253CCBC10045E2A1 /* main.mm */, + ); + name = example; + sourceTree = ""; + }; + 8309BDF6253CDAAE0045E2A1 /* iOS */ = { + isa = PBXGroup; + children = ( + 8309BDF7253CDAAE0045E2A1 /* LaunchScreen.storyboard */, + 8309BDF8253CDAAE0045E2A1 /* Info-iOS.plist */, + ); + path = iOS; + sourceTree = ""; + }; + 8309BDF9253CDAAE0045E2A1 /* macOS */ = { + isa = PBXGroup; + children = ( + 8309BDFA253CDAAE0045E2A1 /* MainMenu.storyboard */, + 8309BDFB253CDAAE0045E2A1 /* Info-macOS.plist */, + ); + path = macOS; + sourceTree = ""; + }; + 83BBE9E320EB46B800295997 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 050450AC276863B000AB6805 /* QuartzCore.framework */, + 05A275432773BEA20084EF39 /* QuartzCore.framework */, + 05318E0E274C397200A8DE2E /* GameController.framework */, + 8309BDC5253CCCFE0045E2A1 /* AppKit.framework */, + 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */, + 83BBE9EE20EB471C00295997 /* ModelIO.framework */, + 83BBE9EB20EB471700295997 /* Metal.framework */, + 83BBE9EA20EB471700295997 /* MetalKit.framework */, + 83BBE9E820EB46C100295997 /* ModelIO.framework */, + 83BBE9E620EB46BD00295997 /* MetalKit.framework */, + 83BBE9E420EB46B900295997 /* Metal.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 83BBE9F020EB544400295997 /* imgui */ = { + isa = PBXGroup; + children = ( + 5079822D257677DB0038A28D /* imgui_tables.cpp */, + 05A2754027728F5B0084EF39 /* imgui_impl_metal4.h */, + 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal4.mm */, + 05A2754127728F5B0084EF39 /* imgui_impl_osx.h */, + 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */, + 83BBEA0420EB54E700295997 /* imconfig.h */, + 83BBEA0320EB54E700295997 /* imgui.cpp */, + 83BBEA0020EB54E700295997 /* imgui.h */, + 83BBEA0220EB54E700295997 /* imgui_demo.cpp */, + 83BBEA0120EB54E700295997 /* imgui_draw.cpp */, + 07A82ED62139413C0078D120 /* imgui_internal.h */, + 07A82ED72139413C0078D120 /* imgui_widgets.cpp */, + ); + name = imgui; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8307E7C320E9F9C900473790 /* example_apple_metal_ios */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8307E7F020E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_ios" */; + buildPhases = ( + 8307E7C020E9F9C900473790 /* Sources */, + 8307E7C120E9F9C900473790 /* Frameworks */, + 8307E7C220E9F9C900473790 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = example_apple_metal_ios; + productName = "imguiex iOS"; + productReference = 8307E7C420E9F9C900473790 /* example_apple_metal.app */; + productType = "com.apple.product-type.application"; + }; + 8307E7D920E9F9C900473790 /* example_apple_metal_macos */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8307E7F320E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_macos" */; + buildPhases = ( + 8307E7D620E9F9C900473790 /* Sources */, + 8307E7D720E9F9C900473790 /* Frameworks */, + 8307E7D820E9F9C900473790 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = example_apple_metal_macos; + productName = "imguiex macOS"; + productReference = 8307E7DA20E9F9C900473790 /* example_apple_metal.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8307E7B620E9F9C700473790 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1530; + ORGANIZATIONNAME = "Warren Moore"; + TargetAttributes = { + 8307E7C320E9F9C900473790 = { + CreatedOnToolsVersion = 9.4.1; + ProvisioningStyle = Automatic; + }; + 8307E7D920E9F9C900473790 = { + CreatedOnToolsVersion = 9.4.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 8307E7B920E9F9C700473790 /* Build configuration list for PBXProject "example_apple_metal" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8307E7B520E9F9C700473790; + productRefGroup = 8307E7C520E9F9C900473790 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8307E7C320E9F9C900473790 /* example_apple_metal_ios */, + 8307E7D920E9F9C900473790 /* example_apple_metal_macos */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8307E7C220E9F9C900473790 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BDFC253CDAB30045E2A1 /* LaunchScreen.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8307E7D820E9F9C900473790 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BE04253CDAB60045E2A1 /* MainMenu.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8307E7C020E9F9C900473790 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BDBB253CCCAD0045E2A1 /* imgui_impl_metal4.mm in Sources */, + 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */, + 83BBEA0720EB54E700295997 /* imgui_demo.cpp in Sources */, + 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */, + 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */, + 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */, + 8309BDA5253CCC070045E2A1 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8307E7D620E9F9C900473790 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8309BDBE253CCCB60045E2A1 /* imgui_impl_metal4.mm in Sources */, + 8309BDBF253CCCB60045E2A1 /* imgui_impl_osx.mm in Sources */, + 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */, + 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */, + 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */, + 050450AB2768052600AB6805 /* imgui_tables.cpp in Sources */, + 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */, + 8309BDA8253CCC080045E2A1 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8307E7EE20E9F9C900473790 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + }; + name = Debug; + }; + 8307E7EF20E9F9C900473790 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + }; + name = Release; + }; + 8307E7F120E9F9C900473790 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/iOS/Info-iOS.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-ios"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + }; + name = Debug; + }; + 8307E7F220E9F9C900473790 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/iOS/Info-iOS.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-ios"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 8307E7F420E9F9C900473790 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/macOS/Info-macOS.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-macos"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + }; + name = Debug; + }; + 8307E7F520E9F9C900473790 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/macOS/Info-macOS.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = "org.imgui.example.apple-metal-macos"; + PRODUCT_NAME = example_apple_metal; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../**"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8307E7B920E9F9C700473790 /* Build configuration list for PBXProject "example_apple_metal" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8307E7EE20E9F9C900473790 /* Debug */, + 8307E7EF20E9F9C900473790 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8307E7F020E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8307E7F120E9F9C900473790 /* Debug */, + 8307E7F220E9F9C900473790 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8307E7F320E9F9C900473790 /* Build configuration list for PBXNativeTarget "example_apple_metal_macos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8307E7F420E9F9C900473790 /* Debug */, + 8307E7F520E9F9C900473790 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8307E7B620E9F9C700473790 /* Project object */; +} diff --git a/examples/example_apple_metal4/iOS/Info-iOS.plist b/examples/example_apple_metal4/iOS/Info-iOS.plist new file mode 100644 index 000000000..93ef078d0 --- /dev/null +++ b/examples/example_apple_metal4/iOS/Info-iOS.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + imgui + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + metal + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/example_apple_metal4/iOS/LaunchScreen.storyboard b/examples/example_apple_metal4/iOS/LaunchScreen.storyboard new file mode 100644 index 000000000..12c52cfbf --- /dev/null +++ b/examples/example_apple_metal4/iOS/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example_apple_metal4/macOS/Info-macOS.plist b/examples/example_apple_metal4/macOS/Info-macOS.plist new file mode 100644 index 000000000..6f4a2b236 --- /dev/null +++ b/examples/example_apple_metal4/macOS/Info-macOS.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + imgui + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSMainStoryboardFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/example_apple_metal4/macOS/MainMenu.storyboard b/examples/example_apple_metal4/macOS/MainMenu.storyboard new file mode 100644 index 000000000..38ad432b0 --- /dev/null +++ b/examples/example_apple_metal4/macOS/MainMenu.storyboard @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example_apple_metal4/main.mm b/examples/example_apple_metal4/main.mm new file mode 100644 index 000000000..1102e41f8 --- /dev/null +++ b/examples/example_apple_metal4/main.mm @@ -0,0 +1,366 @@ +// Dear ImGui: standalone example application for OSX + Metal. + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#import + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#import +#import + +#include "imgui.h" +#include "imgui_impl_metal4.h" +#if TARGET_OS_OSX +#include "imgui_impl_osx.h" +@interface AppViewController : NSViewController +@end +#else +@interface AppViewController : UIViewController +@end +#endif + +@interface AppViewController () +@property (nonatomic, readonly) MTKView *mtkView; +@property (nonatomic, strong) id device; +@property (nonatomic, strong) id commandQueue; +@property (nonatomic, strong) id commandAllocator; +@end + +#define FRAMES_IN_FLIGHT 2 + +//----------------------------------------------------------------------------------- +// AppViewController +//----------------------------------------------------------------------------------- + +@implementation AppViewController + +-(instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + + _device = MTLCreateSystemDefaultDevice(); + _commandQueue = [_device newMTL4CommandQueue]; + _commandAllocator = [_device newCommandAllocator]; + + if (!self.device) + { + NSLog(@"Metal is not supported"); + abort(); + } + + // Setup Dear ImGui context + // FIXME: This example doesn't have proper cleanup... + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. + + // Setup Renderer backend + ImGui_ImplMetal4_Init(_device, _commandQueue, FRAMES_IN_FLIGHT); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + return self; +} + +-(MTKView *)mtkView +{ + return (MTKView *)self.view; +} + +-(void)loadView +{ + self.view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, 1200, 800)]; +} + +-(void)viewDidLoad +{ + [super viewDidLoad]; + + self.mtkView.device = self.device; + self.mtkView.delegate = self; + +#if TARGET_OS_OSX + ImGui_ImplOSX_Init(self.view); + [NSApp activateIgnoringOtherApps:YES]; +#endif +} + +-(void)drawInMTKView:(MTKView*)view +{ + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize.x = view.bounds.size.width; + io.DisplaySize.y = view.bounds.size.height; + +#if TARGET_OS_OSX + CGFloat framebufferScale = view.window.screen.backingScaleFactor ?: NSScreen.mainScreen.backingScaleFactor; +#else + CGFloat framebufferScale = view.window.screen.scale ?: UIScreen.mainScreen.scale; +#endif + io.DisplayFramebufferScale = ImVec2(framebufferScale, framebufferScale); + [self.commandAllocator reset]; + + id commandBuffer = [self.device newCommandBuffer]; + [commandBuffer beginCommandBufferWithAllocator:self.commandAllocator]; + + MTL4RenderPassDescriptor* renderPassDescriptor = view.currentMTL4RenderPassDescriptor; + if (renderPassDescriptor == nil) + { + [commandBuffer endCommandBuffer]; + [self.commandQueue commit:&commandBuffer count:1]; + return; + } + + // Start the Dear ImGui frame + static int frameIndex = 0; + ImGui_ImplMetal4_NewFrame(renderPassDescriptor, frameIndex); + frameIndex++; + frameIndex = (frameIndex + 1) % FRAMES_IN_FLIGHT; + +#if TARGET_OS_OSX + ImGui_ImplOSX_NewFrame(view); +#endif + ImGui::NewFrame(); + + // Our state (make them static = more or less global) as a convenience to keep the example terse. + static bool show_demo_window = true; + static bool show_another_window = false; + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + + renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; + [renderEncoder pushDebugGroup:@"Dear ImGui rendering"]; + ImGui_ImplMetal4_RenderDrawData(draw_data, commandBuffer, renderEncoder); + [renderEncoder popDebugGroup]; + [renderEncoder endEncoding]; + + // Present + [commandBuffer endCommandBuffer]; + [self.commandQueue waitForDrawable:view.currentDrawable]; + [self.commandQueue commit:&commandBuffer count:1]; + [self.commandQueue signalDrawable:view.currentDrawable]; + [view.currentDrawable present]; +} + +-(void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size +{ +} + +//----------------------------------------------------------------------------------- +// Input processing +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +- (void)viewWillAppear +{ + [super viewWillAppear]; + self.view.window.delegate = self; +} + +- (void)windowWillClose:(NSNotification *)notification +{ + ImGui_ImplMetal4_Shutdown(); + ImGui_ImplOSX_Shutdown(); + ImGui::DestroyContext(); +} + +#else + +// This touch mapping is super cheesy/hacky. We treat any touch on the screen +// as if it were a depressed left mouse button, and we don't bother handling +// multitouch correctly at all. This causes the "cursor" to behave very erratically +// when there are multiple active touches. But for demo purposes, single-touch +// interaction actually works surprisingly well. +-(void)updateIOWithTouchEvent:(UIEvent *)event +{ + UITouch *anyTouch = event.allTouches.anyObject; + CGPoint touchLocation = [anyTouch locationInView:self.view]; + ImGuiIO &io = ImGui::GetIO(); + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + io.AddMousePosEvent(touchLocation.x, touchLocation.y); + + BOOL hasActiveTouch = NO; + for (UITouch *touch in event.allTouches) + { + if (touch.phase != UITouchPhaseEnded && touch.phase != UITouchPhaseCancelled) + { + hasActiveTouch = YES; + break; + } + } + io.AddMouseButtonEvent(0, hasActiveTouch); +} + +-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } +-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } +-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } +-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self updateIOWithTouchEvent:event]; } + +#endif + +@end + +//----------------------------------------------------------------------------------- +// AppDelegate +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +@interface AppDelegate : NSObject +@property (nonatomic, strong) NSWindow *window; +@end + +@implementation AppDelegate + +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + return YES; +} + +-(instancetype)init +{ + if (self = [super init]) + { + NSViewController *rootViewController = [[AppViewController alloc] initWithNibName:nil bundle:nil]; + self.window = [[NSWindow alloc] initWithContentRect:NSZeroRect + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable + backing:NSBackingStoreBuffered + defer:NO]; + self.window.contentViewController = rootViewController; + [self.window center]; + [self.window makeKeyAndOrderFront:self]; + } + return self; +} + +@end + +#else + +@interface AppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@end + +@implementation AppDelegate + +-(BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + UIViewController *rootViewController = [[AppViewController alloc] init]; + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end + +#endif + +//----------------------------------------------------------------------------------- +// Application main() function +//----------------------------------------------------------------------------------- + +#if TARGET_OS_OSX + +int main(int, const char**) +{ + @autoreleasepool + { + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + AppDelegate *appDelegate = [[AppDelegate alloc] init]; // creates window + [NSApp setDelegate:appDelegate]; + + [NSApp activateIgnoringOtherApps:YES]; + [NSApp run]; + } + return 0; +} + +#else + +int main(int argc, char * argv[]) +{ + @autoreleasepool + { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + +#endif From 9bb131fb2f866dba6be12c3a73023e984e18a959 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 8 Jul 2026 18:28:37 +0200 Subject: [PATCH 114/254] CI: enable Metal4 examples. (#9466, #9451) (Untested) --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7e1095c9..540578ef4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -509,7 +509,7 @@ jobs: run: g++ -c -I. -std=c++11 -DIMGUI_IMPL_VULKAN_NO_PROTOTYPES=1 backends/imgui_impl_vulkan.cpp Build-MacOS: - runs-on: macos-latest + runs-on: macos-26 name: Build - MacOS defaults: @@ -575,8 +575,8 @@ jobs: - name: Build macOS example_sdl2_metal run: make -C examples/example_sdl2_metal - #- name: Build macOS example_sdl3_metal4 - # run: make -C examples/example_sdl3_metal4 + - name: Build macOS example_sdl3_metal4 + run: make -C examples/example_sdl3_metal4 - name: Build macOS example_sdl2_opengl2 run: make -C examples/example_sdl2_opengl2 @@ -593,9 +593,9 @@ jobs: run: xcodebuild -project examples/example_apple_metal/example_apple_metal.xcodeproj -target example_apple_metal_macos if: github.event_name == 'workflow_run' - #- name: Build macOS example_apple_metal4 - # run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos - # if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_metal4 + run: xcodebuild -project examples/example_apple_metal4/example_apple_metal4.xcodeproj -target example_apple_metal_macos + if: github.event_name == 'workflow_run' - name: Build macOS example_apple_opengl2 run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 From da137cbbb066e57f4b44f3f331c36e1e30e1cbe2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 8 Jul 2026 18:40:29 +0200 Subject: [PATCH 115/254] (Breaking) Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload`. Use `ImGuiDragDropFlags_PayloadAutoExpire`. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 1 + imgui.h | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 373da111f..ed02e3f0b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -45,6 +45,8 @@ Breaking Changes: was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. +- Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` + which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. Other Changes: diff --git a/imgui.cpp b/imgui.cpp index 2954bd6eb..47c938977 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/07/08 (1.92.9) - Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. - 2026/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. - 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. - 2026/05/07 (1.92.8) - DrawList: swapped the last two arguments of AddRect(), AddPolyline(), PathStroke(). diff --git a/imgui.h b/imgui.h index d63544c12..1fa433837 100644 --- a/imgui.h +++ b/imgui.h @@ -1503,7 +1503,7 @@ enum ImGuiDragDropFlags_ ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect, // For peeking ahead and inspecting the payload before delivery. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 + //ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 #endif }; From b62bfd6b06de958e4630b715225b7e8409bfd0f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Jul 2026 21:36:43 +0200 Subject: [PATCH 116/254] InputText: reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing Ctrl+Enter or Shift+Enter still allows to deactivate. cc #9239 --- docs/CHANGELOG.txt | 2 ++ imgui.h | 2 +- imgui_widgets.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ed02e3f0b..8214f0548 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -63,6 +63,8 @@ Other Changes: - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. + - Reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing + Ctrl+Enter or Shift+Enter still allows to deactivate. (#9239) - Tables: - Redesigned/rewrote code to reconcile columns and settings on topology changes. (#9108) - When a column label is passed to TableSetupColumn(), the underlying identifier diff --git a/imgui.h b/imgui.h index 1fa433837..0216b893b 100644 --- a/imgui.h +++ b/imgui.h @@ -2442,7 +2442,7 @@ struct ImGuiIO bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). - bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). + bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). Ctrl+Enter or Shift+Enter will deactivate normally. ImGuiColorEditFlags ConfigColorEditFlags; // = // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions. bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c787a0bbd..11b7da0b8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5173,7 +5173,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_new_line) { validated = clear_active_id = true; - if (io.ConfigInputTextEnterKeepActive && !is_multiline) + if (io.ConfigInputTextEnterKeepActive && !is_multiline && !is_ctrl_enter && !is_shift_enter) { // Queue reactivation, so that e.g. IsItemDeactivatedAfterEdit() will work. (#9001) state->SelectAll(); // No need to scroll From 003ee19d9e6203b5c68467c0e6c86d873c125e45 Mon Sep 17 00:00:00 2001 From: ShiroKSH Date: Mon, 13 Jul 2026 21:28:48 +0300 Subject: [PATCH 117/254] Backends: SDL_Renderer3: fixed default sampler not being Linear. (#7616, #9470, #9378) --- backends/imgui_impl_sdlrenderer3.cpp | 5 +++++ docs/CHANGELOG.txt | 2 ++ 2 files changed, 7 insertions(+) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index bf0250401..de9eae442 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -25,6 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: Fixed default sampler state to be linear (broken 2026-04-23). (#9470, #9378) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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. @@ -77,10 +78,14 @@ static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData() // Functions static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer) { + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + // Clear out any viewports and cliprect set by the user // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process. SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderClipRect(renderer, nullptr); + + bd->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } void ImGui_ImplSDLRenderer3_NewFrame() diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8214f0548..9fa7972a9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -158,6 +158,8 @@ Other Changes: Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) + - SDLRenderer3: + - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] - Win32: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` when available, fixing OpenGL DPI scaling issues as e.g. NVIDIA drivers tends From bad1ee71f89103e6e9f08b761d35548ef7034555 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 13:42:21 +0200 Subject: [PATCH 118/254] Backends: SDL_Renderer3: fixed changing scale mode not actually working on all platforms. (#7616, #9470, #9378) --- backends/imgui_impl_sdlrenderer3.cpp | 4 ++++ docs/CHANGELOG.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index de9eae442..d4c043cbc 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -171,6 +171,7 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = render_scale; + SDL_ScaleMode last_scale_mode = bd->CurrentScaleMode; // Render command lists for (const ImDrawList* draw_list : draw_data->CmdLists) @@ -208,6 +209,9 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv)); const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+ + if (last_scale_mode != bd->CurrentScaleMode) + SDL_FlushRenderer(renderer); + // Bind texture, Draw SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); SDL_SetTextureScaleMode(tex, bd->CurrentScaleMode); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9fa7972a9..935ff19ea 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -159,6 +159,7 @@ Other Changes: - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) - SDLRenderer3: + - Fixed sampler change which didn't work on all graphics backends. (#7616, #9470, #9378) - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] - Win32: - Uses `SetProcessDpiAwarenessContext()` instead of `SetThreadDpiAwarenessContext()` From addbfa50214f1e2302a1439b10c722cab35c2c0c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 13:57:48 +0200 Subject: [PATCH 119/254] Settings: fixed ConfigIniSettingsAutoDiscardMonths not calling Cleanup (#9460, #9471) --- imgui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 47c938977..d8e790ea1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15809,7 +15809,10 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) // Call post-read handlers ImGuiSettingsCleanupArgs cleanup_args; if (g.IO.ConfigIniSettingsAutoDiscardMonths > 0) + { cleanup_args.DiscardOlderThanMonths = g.IO.ConfigIniSettingsAutoDiscardMonths; + CleanupIniSettings(&cleanup_args); + } for (ImGuiSettingsHandler& handler : g.SettingsHandlers) if (handler.ApplyAllFn != NULL) handler.ApplyAllFn(&g, &handler); From 0720d59bb734901f3c6bf760341fedac83868fb1 Mon Sep 17 00:00:00 2001 From: ShiroKSH Date: Mon, 13 Jul 2026 21:34:14 +0300 Subject: [PATCH 120/254] Tables: validate ini settings column count. (#9472) + Amend with comment by ocornut. --- imgui_tables.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e1328fe59..efc03d7d9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -4165,9 +4165,12 @@ static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandle static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { + // FIXME: As topology changes are allowed, strictly speaking a >= IMGUI_TABLE_MAX_COLUMNS tables stored in .ini file + // that was emitted with a higher max count could still be meaningful in some unlikely cases. + // We might want to use another MAX defined as (1<<(sizeof(ImGuiTableColumnIdx)*8-1))-1. ImGuiID id = 0; int columns_count = 0; - if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) + if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2 || columns_count <= 0 || columns_count >= IMGUI_TABLE_MAX_COLUMNS) return NULL; return ImGui::TableSettingsCreate(id, columns_count); } From 0302703a7dc290dbed3a312a1fb288e8e07d1232 Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 07:36:59 -0500 Subject: [PATCH 121/254] CI: Android: Gradle 9.4.1 via setup-gradle action for AGP 9.2.0 (#8878) (#9467) --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 540578ef4..5bbaefd1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -661,10 +661,10 @@ jobs: name: Build - Android steps: - #- name: Setup Gradle - # uses: gradle/actions/setup-gradle@v6 - # with: - # gradle-version: '8.14.5' + - name: Setup Gradle 9.4.1 + uses: gradle/actions/setup-gradle@v6 + with: + gradle-version: '9.4.1' - uses: actions/checkout@v6 - name: Build example_android_opengl3 From 909082890a404ceba3f4b0f624db73f04fc9288a Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 09:37:12 -0500 Subject: [PATCH 122/254] Backends: Android: clear mouse position on touch release. (#6627, #9474) --- backends/imgui_impl_android.cpp | 5 ++++- docs/CHANGELOG.txt | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index a76de1c26..f324c7dce 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -23,7 +23,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). +// 2026-07-15: Inputs: clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent items from staying in hovered state. (#6627, #9474) +// 2022-09-26: Inputs: renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. // 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+). // 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range. @@ -230,6 +231,8 @@ int32_t ImGui_ImplAndroid_HandleInputEvent(const AInputEvent* input_event) { io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index)); io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN); + if (event_action == AMOTION_EVENT_ACTION_UP) // (#6627, #9474) + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } break; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 935ff19ea..05aaa3162 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -150,6 +150,9 @@ Other Changes: - Misc: - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: + - Android: + - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent + items from staying in hovered state. (#6627, #9474) [@Turtle-PB] - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] From 35a7e8e864ac3a7f8a3869029ff48d404fea56bb Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 09:37:12 -0500 Subject: [PATCH 123/254] Backends: SDL2: restore SDL_StartTextInput()/SDL_StopTextInput() calls on Android (#7636, #9474) --- backends/imgui_impl_android.cpp | 1 + backends/imgui_impl_sdl2.cpp | 13 +++++++++++++ docs/CHANGELOG.txt | 7 +++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index f324c7dce..08b3de08a 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -24,6 +24,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-07-15: Inputs: clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent items from staying in hovered state. (#6627, #9474) +// 2023-04-11: Inputs: calling new io.AddMouseSourceEvent() to discriminate Mouse from Touch events. // 2022-09-26: Inputs: renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. // 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+). diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index 4def68dd6..eec4e0075 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: Inputs: restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard support on Android/mobile. (#7636, #9474) // 2026-04-16: Made ImGui_ImplSDL2_GetContentScaleForWindow(), ImGui_ImplSDL2_GetContentScaleForDisplay() helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) // 2026-02-13: Inputs: systems other than X11 are back to starting mouse capture on mouse down (reverts 2025-02-26 change). Only X11 requires waiting for a drag by default (not ideal, but a better default for X11 users). Added ImGui_ImplSDL2_SetMouseCaptureMode() for X11 debugger users. (#3650, #6410, #9235) // 2026-01-15: Changed GetClipboardText() handler to return nullptr on error aka clipboard contents is not text. Consistent with other backends. (#9168) @@ -205,6 +206,18 @@ static void ImGui_ImplSDL2_PlatformSetImeData(ImGuiContext*, ImGuiViewport*, ImG r.w = 1; r.h = (int)data->InputLineHeight; SDL_SetTextInputRect(&r); + +#ifdef __ANDROID__ + // SDL_StartTextInput() is needed on Android (and some other platforms) to show the on-screen keyboard. (#7636, #9474, #6306) + // It was removed in a7703fe6 due to concerns about its relation to desktop IME, but is required on mobile. + SDL_StartTextInput(); +#endif + } + else + { +#ifdef __ANDROID__ + SDL_StopTextInput(); +#endif } } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 05aaa3162..f4f699220 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -151,8 +151,8 @@ Other Changes: - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: - Android: - - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent - items from staying in hovered state. (#6627, #9474) [@Turtle-PB] + - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent + items from staying in hovered state. (#6627, #9474) [@Turtle-PB] - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] @@ -161,6 +161,9 @@ Other Changes: Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) + - SDL2: + - Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard + support on Android. (#7636, #9474) [@Turtle-PB] - SDLRenderer3: - Fixed sampler change which didn't work on all graphics backends. (#7616, #9470, #9378) - Fixed default sampler not being Linear. Regression in 1.92.8. (#7616, #9470, #9378) [@ShiroKSH] From e3927dc004a05f4cc7b4ebe5f1fe204efbbb4f29 Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Wed, 15 Jul 2026 07:58:39 -0500 Subject: [PATCH 124/254] Backends: OpenGL2/3: Backup and restore GL_UNPACK state in UpdateTexture() (#9473, #8802) --- backends/imgui_impl_opengl2.cpp | 11 +++++++++++ backends/imgui_impl_opengl3.cpp | 20 +++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 6cbaaac77..23afa1d33 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -30,6 +30,7 @@ // 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) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748) @@ -262,6 +263,12 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { + // Backup GL_UNPACK state that we modify, restore on exit. + // This prevents corrupting the caller's pixel store state when ImGui + // creates or updates textures mid-frame. (#8802, #9473) + GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); + GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); + if (tex->Status == ImTextureStatus_WantCreate) { // Create and upload new texture to graphics system @@ -318,6 +325,10 @@ void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) tex->SetTexID(ImTextureID_Invalid); tex->SetStatus(ImTextureStatus_Destroyed); } + + // Restore GL_UNPACK state + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); } bool ImGui_ImplOpenGL2_CreateDeviceObjects() diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 1f96c6fe8..8e21a3a5f 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -32,6 +32,7 @@ // 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) // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy. +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). // 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) @@ -666,7 +667,16 @@ static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { - // FIXME: Consider backing up and restoring + // Backup GL_UNPACK state that we modify, restore on exit. + // This prevents corrupting the caller's pixel store state when ImGui + // creates or updates textures mid-frame. (#8802, #9473) +#ifdef GL_UNPACK_ROW_LENGTH + GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); +#endif +#ifdef GL_UNPACK_ALIGNMENT + GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); +#endif + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) { #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES @@ -738,6 +748,14 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) } else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0) ImGui_ImplOpenGL3_DestroyTexture(tex); + + // Restore GL_UNPACK state +#ifdef GL_UNPACK_ROW_LENGTH + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); +#endif +#ifdef GL_UNPACK_ALIGNMENT + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); +#endif } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. From 0ff4a8a9008e38759f2e4273ff90643d1d75c17d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 18:19:07 +0200 Subject: [PATCH 125/254] Backends: OpenGL2/3: Backup and restore GL_UNPACK state in UpdateTexture(). Amends. (#9473, #8802) Fix setting GL_UNPACK_ROW_LENGTH twice. --- backends/imgui_impl_opengl2.cpp | 4 +--- backends/imgui_impl_opengl3.cpp | 31 +++++++++++++------------------ docs/CHANGELOG.txt | 5 +++++ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 23afa1d33..111f37413 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -26,11 +26,11 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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) -// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture(). // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748) @@ -264,8 +264,6 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) { // Backup GL_UNPACK state that we modify, restore on exit. - // This prevents corrupting the caller's pixel store state when ImGui - // creates or updates textures mid-frame. (#8802, #9473) GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 8e21a3a5f..f1f2bc93c 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-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. // 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -32,7 +33,6 @@ // 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) // 2025-07-15: OpenGL: Set GL_UNPACK_ALIGNMENT to 1 before updating textures (#8802) + restore non-WebGL/ES update path that doesn't require a CPU-side copy. -// 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture(). // 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) @@ -668,21 +668,13 @@ static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex) void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) { // Backup GL_UNPACK state that we modify, restore on exit. - // This prevents corrupting the caller's pixel store state when ImGui - // creates or updates textures mid-frame. (#8802, #9473) -#ifdef GL_UNPACK_ROW_LENGTH - GLint last_unpack_row_length = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); -#endif -#ifdef GL_UNPACK_ALIGNMENT - GLint last_unpack_alignment = 0; GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); -#endif - + GLint last_unpack_row_length = 0; (void)last_unpack_row_length; + GLint last_unpack_alignment = 0; (void)last_unpack_alignment; if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) { #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); -#endif -#ifdef GL_UNPACK_ALIGNMENT + GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); + GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &last_unpack_alignment)); GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); #endif } @@ -706,6 +698,9 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); +#if GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); +#endif GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); // Store identifiers @@ -728,7 +723,6 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width)); for (ImTextureRect& r : tex->Updates) GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y))); - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); #else // GL ES doesn't have GL_UNPACK_ROW_LENGTH, so we need to (A) copy to a contiguous buffer or (B) upload line by line. ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); @@ -750,12 +744,13 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) ImGui_ImplOpenGL3_DestroyTexture(tex); // Restore GL_UNPACK state + if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + { #ifdef GL_UNPACK_ROW_LENGTH - GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); -#endif -#ifdef GL_UNPACK_ALIGNMENT - GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); + GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, last_unpack_alignment)); #endif + } } // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f4f699220..d539a3c3e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -156,11 +156,16 @@ Other Changes: - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] + - OpenGL2: + - Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture + to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to dynamically select between use of glBindSampler() and glTexParameter(). (#9378) + - Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture + to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] - SDL2: - Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard support on Android. (#7636, #9474) [@Turtle-PB] From baeea39c1a09ff811728c22e3f35068f1e86154d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Jul 2026 16:32:07 +0200 Subject: [PATCH 126/254] Settings: fixed issues caliing ClearWindowSettings() followed by LoadIniSettingsFromMemory(). While not done with core, this would be useful to load .ini over a clean slate. --- imgui.cpp | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d8e790ea1..7e82a5ed8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6710,12 +6710,34 @@ ImGuiWindow* ImGui::FindWindowByName(const char* name) static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { - window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y)); - if (settings->Size.x > 0 && settings->Size.y > 0) - window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y)); - window->Collapsed = settings->Collapsed; + if (settings != NULL) + { + window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y)); + if (settings->Size.x > 0 && settings->Size.y > 0) + { + window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y)); + window->AutoFitFramesX = window->AutoFitFramesY = 0; + } + window->Collapsed = settings->Collapsed; + SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); + } + + window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values + if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) + { + window->AutoFitFramesX = window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } + else + { + window->AutoFitFramesX = (window->Size.x <= 0.0f) ? 2 : 0; + window->AutoFitFramesY = (window->Size.y <= 0.0f) ? 2 : 0; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } } +// Note that 'settings' may be NULL. +// Sets _Once, _Appearing, _FirstUseEver. _FirstUseEver will be cleared again if there are settings. static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { // Initial window state with e.g. default/arbitrary window position @@ -6725,28 +6747,9 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s window->Pos = main_viewport->Pos + ImVec2(60, 60); window->Size = window->SizeFull = ImVec2(0, 0); window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - + ApplyWindowSettings(window, settings); if (settings != NULL) - { settings->LastUsedDate = g.SessionDate; - SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); - ApplyWindowSettings(window, settings); - } - window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values - - if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) - { - window->AutoFitFramesX = window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = false; - } - else - { - if (window->Size.x <= 0.0f) - window->AutoFitFramesX = 2; - if (window->Size.y <= 0.0f) - window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); - } } static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) From 2392a52660a2e031bdacab239fd0e9073fb1563c Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Apr 2026 21:34:27 +0200 Subject: [PATCH 127/254] InputText: tag InputTextDeactivatedState with an elapsing frame. Avoid unnecessary InputTextDeactivateHook() call on manual deactivation. InputTextDeactivateHook() only takes a record when Edited + callback marks edited. (#9476, #701) for _NoLiveEdit it's easier than we don't use IsItemDeactivatedAfterEdit() in InputText()'s `if (g.InputTextDeactivatedState.ID == id)` block. --- imgui.cpp | 2 ++ imgui_internal.h | 3 ++- imgui_widgets.cpp | 11 ++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7e82a5ed8..2fbc33f6c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5686,6 +5686,8 @@ void ImGui::NewFrame() if (g.DeactivatedItemData.ElapseFrame < g.FrameCount) g.DeactivatedItemData.ID = 0; g.DeactivatedItemData.IsAlive = false; + if (g.InputTextDeactivatedState.ElapseFrame < g.FrameCount) + g.InputTextDeactivatedState.ID = 0; // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. diff --git a/imgui_internal.h b/imgui_internal.h index 23b8ec0fd..b92274846 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1239,10 +1239,11 @@ struct IMGUI_API ImGuiMenuColumns struct IMGUI_API ImGuiInputTextDeactivatedState { ImGuiID ID; // widget id owning the text state (which just got deactivated) + int ElapseFrame; ImVector TextA; // text buffer ImGuiInputTextDeactivatedState() { memset((void*)this, 0, sizeof(*this)); } - void ClearFreeMemory() { ID = 0; TextA.clear(); } + void ClearFreeMemory() { ID = 0; ElapseFrame = 0; TextA.clear(); } }; // Forward declare imstb_textedit.h structure + make its main configuration define accessible diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 11b7da0b8..f1517273a 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4586,8 +4586,11 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) ImGuiInputTextState* state = &g.InputTextState; if (id == 0 || state->ID != id) return; + if (!state->EditedBefore) + return; //IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id); g.InputTextDeactivatedState.ID = state->ID; + g.InputTextDeactivatedState.ElapseFrame = g.FrameCount + 1; if (state->Flags & ImGuiInputTextFlags_ReadOnly) { g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat. @@ -5380,6 +5383,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ 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->EditedBefore = state->EditedThisFrame = true; state->CursorAnimReset(); } } @@ -5399,7 +5403,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // 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) + // The state only exists after an Edit. More-over we cannot use IsItemDeactivatedAfterEdit(). + if (g.ActiveId != id && IsItemDeactivated() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) { apply_new_text = g.InputTextDeactivatedState.TextA.Data; apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; @@ -5445,7 +5450,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) // Otherwise request text input ahead for next frame. if (g.ActiveId == id && clear_active_id) + { + state->ID = 0; // To avoid InputTextDeactivateHook() unnecessarily running, which wouldn't be harmful but wasteful. ClearActiveID(); + state->ID = id; + } // Render frame if (!is_multiline) From 18d63b12bef5f902b4d2349b10e874add7b62802 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Apr 2026 15:57:36 +0200 Subject: [PATCH 128/254] Added ImGuiItemFlags_LiveEdit flag, and much-awaited support for disabling it. (#9476, #701) cc #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,#5777, #6707, #6766, #8004, #8303, #8915, #9308 --- imgui.cpp | 7 +++-- imgui.h | 5 +++- imgui_internal.h | 4 +-- imgui_widgets.cpp | 76 +++++++++++++++++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2fbc33f6c..5c7f4df5c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4833,6 +4833,7 @@ void ImGui::MarkItemEdited(ImGuiID id) // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. ImGuiContext& g = *GImGui; + //IM_ASSERT(g.LastItemData.ID == id); // Failing cases include: "widgets_inputtext_scrolling", "widgets_inputtext_multiline_status", "widgets_selectable_input" = case of e.g TempInputText() overlayed manually with different ID (#2718) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_EditedInternal; if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited) return; @@ -4843,14 +4844,14 @@ void ImGui::MarkItemEdited(ImGuiID id) // FIXME: Can't we fully rely on LastItemData yet? g.ActiveIdHasBeenEditedThisFrame = true; g.ActiveIdHasBeenEditedBefore = true; - if (g.DeactivatedItemData.ID == id) - g.DeactivatedItemData.HasBeenEditedBefore = true; } + if (g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.HasBeenEditedBefore = true; // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) // FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction. - IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); + IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.DeactivatedItemData.ID == id || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); } bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) diff --git a/imgui.h b/imgui.h index 0216b893b..672f24e66 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.9 WIP" -#define IMGUI_VERSION_NUM 19285 +#define IMGUI_VERSION_NUM 19286 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1246,6 +1246,8 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). + + ImGuiItemFlags_LiveEdit = 1 << 7, // true // WIP }; // Flags for ImGui::InputText() @@ -1275,6 +1277,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + //ImGuiInputTextFlags_NoLiveEdit = 1 << 25, // Elide display / Alignment ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! diff --git a/imgui_internal.h b/imgui_internal.h index b92274846..2d456b98d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() - ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead. + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEdit, // Please don't change, use PushItemFlag() instead. // Obsolete //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior @@ -3799,7 +3799,7 @@ namespace ImGui IMGUI_API void InputTextDeactivateHook(ImGuiID id); IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL); - inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return g.ActiveId == id && g.TempInputId == id; } + inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.TempInputId == id && g.ActiveId == id) || (g.InputTextDeactivatedState.ID == id); } inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data); inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f1517273a..e790eeeb8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3722,8 +3722,9 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const bool init = (g.TempInputId != id); - if (init) + const bool is_deactivated = (g.InputTextDeactivatedState.ID == id); + const bool is_active = (g.TempInputId == id); + if (!is_active && !is_deactivated) ClearActiveID(); ImVec2 backup_pos = window->DC.CursorPos; @@ -3731,13 +3732,13 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; // Using ImGuiInputTextFlags_MergedItem above will skip ItemAdd() so we poke here. bool value_changed = InputTextEx(label, NULL, buf, (int)buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_TempInput | ImGuiInputTextFlags_AutoSelectAll, callback, user_data); KeepAliveID(id); // Not done because of ImGuiInputTextFlags_TempInput - if (init) + if (!is_active && !is_deactivated) { // First frame we started displaying the InputText widget, we expect it to take the active id. IM_ASSERT(g.ActiveId == id); g.TempInputId = g.ActiveId; } - if (g.ActiveId != id) + if (is_active && g.ActiveId != id) g.TempInputId = 0; window->DC.CursorPos = backup_pos; return value_changed; @@ -3841,8 +3842,23 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data } // Apply - bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. - bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false; + bool value_changed = false; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + { + bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. + if (input_edited) + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + } + else + { + //g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_Edited; + if (g.DeactivatedItemData.ID == g.LastItemData.ID) + { + //g.DeactivatedItemData.HasBeenEditedBefore = false; // Will be set below by MarkItemEdited() + //if (IsItemDeactivated()) // Should be unnecessary + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + } + } // Step buttons if (has_step_buttons) @@ -4584,7 +4600,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiInputTextState* state = &g.InputTextState; - if (id == 0 || state->ID != id) + if (id == 0 || state->ID != id || g.ActiveId != id) return; if (!state->EditedBefore) return; @@ -4820,7 +4836,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool user_clicked = hovered && io.MouseClicked[0]; 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 input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput)); + const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput) && g.InputTextDeactivatedState.ID != id); 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; @@ -4852,7 +4868,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnimReset(); // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) - InputTextDeactivateHook(state->ID); + if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) + InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below. // Take a copy of the initial buffer value. // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) @@ -5389,13 +5406,27 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } } - // Will copy result string if modified. + // Write back result string if modified. // FIXME-OPT: Could mark dirty state from the stb_textedit callbacks - if (!is_readonly && strcmp(state->TextSrc, buf) != 0) + if (!is_readonly) { - apply_new_text = state->TextSrc; - apply_new_text_length = state->TextLen; - value_changed = true; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + { + // Apply when modified + if (strcmp(state->TextSrc, buf) != 0) + { + apply_new_text = state->TextSrc; + apply_new_text_length = state->TextLen; + value_changed = true; + } + } + else + { + // Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert) + value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0); + apply_new_text = value_changed ? state->TextSrc : NULL; + apply_new_text_length = value_changed ? state->TextLen : 0; + } } } @@ -5403,14 +5434,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // 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) { - // The state only exists after an Edit. More-over we cannot use IsItemDeactivatedAfterEdit(). - if (g.ActiveId != id && IsItemDeactivated() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) - { - apply_new_text = g.InputTextDeactivatedState.TextA.Data; - apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; - value_changed = true; - //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); - } + // The state only exists after an Edit. IsItemDeactivatedAfterEdit() is not valid in every code path (see "widgets_inputtext_status_noliveedit" test). + if ((g.ActiveId != id && IsItemDeactivated()) || (g.ActiveId == id && (flags & ImGuiInputTextFlags_TempInput))) + if (!is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) + { + apply_new_text = g.InputTextDeactivatedState.TextA.Data; + apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; + value_changed = true; + //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); + } g.InputTextDeactivatedState.ID = 0; } From 4853f5c96b34a00dfce921270f6c0abfa06f682d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 19:46:54 +0200 Subject: [PATCH 129/254] Internals: rename NextItemData.ItemFlags -> ItemFlagsSet --- imgui.cpp | 6 +++--- imgui_internal.h | 4 ++-- imgui_widgets.cpp | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5c7f4df5c..5eec3950a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6435,7 +6435,7 @@ bool ImGui::IsItemEdited() void ImGui::SetNextItemAllowOverlap() { ImGuiContext& g = *GImGui; - g.NextItemData.ItemFlags |= ImGuiItemFlags_AllowOverlap; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_AllowOverlap; } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -11445,7 +11445,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.LastItemData.ID = id; g.LastItemData.Rect = bb; g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; - g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; + g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlagsSet | extra_flags; g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. @@ -11479,7 +11479,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // Lightweight clear of SetNextItemXXX data. g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None; - g.NextItemData.ItemFlags = ImGuiItemFlags_None; + g.NextItemData.ItemFlagsSet = ImGuiItemFlags_None; #ifdef IMGUI_ENABLE_TEST_ENGINE if (id != 0) diff --git a/imgui_internal.h b/imgui_internal.h index 2d456b98d..80e369378 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1388,7 +1388,7 @@ enum ImGuiNextItemDataFlags_ struct ImGuiNextItemData { ImGuiNextItemDataFlags HasFlags; // Called HasFlags instead of Flags to avoid mistaking this - ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. + ImGuiItemFlags ItemFlagsSet; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. // Members below are NOT cleared by ItemAdd() meaning they are still valid during e.g. NavProcessItem(). Always rely on HasFlags. ImGuiID FocusScopeId; // Set by SetNextItemSelectionUserData() @@ -1403,7 +1403,7 @@ struct ImGuiNextItemData ImU32 ColorMarker; // Set by SetNextItemColorMarker(). Not exposed yet, supported by DragScalar,SliderScalar and for ImGuiSliderFlags_ColorMarkers. ImGuiNextItemData() { memset((void*)this, 0, sizeof(*this)); SelectionUserData = -1; } - inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! + inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlagsSet = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! }; // Status storage for the last submitted item diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e790eeeb8..d63c5c5e0 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1325,7 +1325,7 @@ bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) if (!all_on && any_on) { ImGuiContext& g = *GImGui; - g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_MixedValue; pressed = Checkbox(label, &all_on); } else @@ -3820,7 +3820,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. - g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoMarkEdited; flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; const bool has_step_buttons = (p_step != NULL); @@ -5707,7 +5707,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)... Dummy(ImVec2(0.0f, text_size_y + style.FramePadding.y)); - g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; + g.NextItemData.ItemFlagsSet |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; EndChild(); item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); @@ -6121,7 +6121,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImGuiIO& io = g.IO; const float width = CalcItemWidth(); - const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; + const bool is_readonly = ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; g.NextItemData.ClearFlags(); PushID(label); @@ -8264,7 +8264,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect) { // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) - g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; if (ms->IO.RangeSrcItem == selection_user_data) ms->RangeSrcPassedBy = true; //ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. @@ -8272,7 +8272,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d } else { - g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_HasSelectionUserData; } } @@ -9427,7 +9427,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // This is only done for items for the menu set and not the full parent window. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) - g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoWindowHoverableCheck; // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). @@ -9652,7 +9652,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) - g.NextItemData.ItemFlags |= ImGuiItemFlags_NoWindowHoverableCheck; + g.NextItemData.ItemFlagsSet |= ImGuiItemFlags_NoWindowHoverableCheck; // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. From c958f16059bfa5de024cd63eb244a65c5d031590 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 21:32:39 +0200 Subject: [PATCH 130/254] EndGroup: fixed reporting combined Edit when it happens at the same time as tabbing to next ActiveId. (#9476, #701) --- imgui.cpp | 9 +++++---- imgui_internal.h | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5eec3950a..24aa23355 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4250,6 +4250,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) HoveredIdIsDisabled = false; HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; ItemUnclipByLog = false; + AnyIdHasBeenEditedThisFrame = false; ActiveId = 0; ActiveIdIsAlive = 0; ActiveIdTimer = 0.0f; @@ -4842,8 +4843,7 @@ void ImGui::MarkItemEdited(ImGuiID id) if (g.ActiveId == id || g.ActiveId == 0) { // FIXME: Can't we fully rely on LastItemData yet? - g.ActiveIdHasBeenEditedThisFrame = true; - g.ActiveIdHasBeenEditedBefore = true; + g.AnyIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedBefore = true; } if (g.DeactivatedItemData.ID == id) g.DeactivatedItemData.HasBeenEditedBefore = true; @@ -5673,6 +5673,7 @@ void ImGui::NewFrame() g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; g.ActiveIdIsAlive = 0; + g.AnyIdHasBeenEditedThisFrame = false; g.ActiveIdHasBeenEditedThisFrame = false; g.ActiveIdIsJustActivated = false; if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) @@ -11889,7 +11890,7 @@ void ImGui::BeginGroup() group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; group_data.BackupIsSameLine = window->DC.IsSameLine; - group_data.BackupActiveIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedThisFrame; + group_data.BackupAnyIdHasBeenEditedThisFrame = g.AnyIdHasBeenEditedThisFrame; group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive; group_data.EmitItem = true; @@ -11954,7 +11955,7 @@ void ImGui::EndGroup() g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; // Forward Edited flag - if (g.ActiveIdHasBeenEditedThisFrame && !group_data.BackupActiveIdHasBeenEditedThisFrame) + if (g.AnyIdHasBeenEditedThisFrame && !group_data.BackupAnyIdHasBeenEditedThisFrame) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; // Forward Deactivated flag diff --git a/imgui_internal.h b/imgui_internal.h index 80e369378..fdff662d0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1209,7 +1209,7 @@ struct IMGUI_API ImGuiGroupData ImVec2 BackupCurrLineSize; float BackupCurrLineTextBaseOffset; ImGuiID BackupActiveIdIsAlive; - bool BackupActiveIdHasBeenEditedThisFrame; + bool BackupAnyIdHasBeenEditedThisFrame; bool BackupDeactivatedIdIsAlive; bool BackupHoveredIdIsAlive; bool BackupIsSameLine; @@ -2312,6 +2312,7 @@ struct ImGuiContext bool HoveredIdAllowOverlap; bool HoveredIdIsDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0. bool ItemUnclipByLog; // Disable ItemAdd() clipping, essentially a memory-locality friendly copy of LogEnabled + bool AnyIdHasBeenEditedThisFrame; ImGuiID ActiveId; // Active widget ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame) float ActiveIdTimer; From 26b82926354edca5a5e09d144bb55dbf139086c8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Jul 2026 21:47:45 +0200 Subject: [PATCH 131/254] Split ImGuiItemFlags_LiveEdit into ImGuiItemFlags_LiveEditText, ImGuiItemFlags_LiveEditScalar. Added Demo contents. (#9476, #701) cc #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,#5777, #6707, #6766, #8004, #8303, #8915, #9308 --- docs/CHANGELOG.txt | 39 ++++++++++++++++++++++++ imgui.cpp | 1 + imgui.h | 25 ++++++++++++--- imgui_demo.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++++ imgui_internal.h | 2 +- imgui_widgets.cpp | 7 +++-- 6 files changed, 141 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d539a3c3e..5cecbdb37 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,45 @@ Other Changes: - Fixed double-click collapse toggle not owning the mouse button. If a `SetNextWindowPos()` with pivot was queued in the same frame, the second click could trigger another item in the same window. (#9439) [@Cleroth] +- Added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar` + flags to configure the timing of applying edits of backing variables when typing + values using a keyboard. + (#701, #9476, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, + #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184, + #5777, #6707, #6766, #8004, #8303, #8915, #9308) + - Until now: + - Edits where always applied immediately to backing variable, which is equivalent + to the `ImGuiItemFlags_LiveEditXXX` flags being enabled. + - Typing '123' in an integer field would output successively 1, 12 then 123. + - Most uses of `IsItemDeactivatedAfterEdit()` or `ImGuiInputTextFlags_EnterReturnsTrue` + were actually workarounds for this issue. Advanced applications would typically + use `IsItemDeactivatedAfterEdit()` to distinguish transactions. + Workarounds often required a backing store for scalar values, and there were + also a few niggles related to `IsItemDeactivatedAfterEdit()` when using +/- + buttons of an `InputInt()` widgets. + Many of those situations can now be naturally simplified by disabling + `ImGuiItemFlags_LiveEditOnInputScalar`, which is expected to become the default. + - The new flags allows disabling this behavior selectively for strings fields + such as `InputText()` vs scalar fields: `SliderInt()`, `InputFloat()`, etc. + - When LiveEdit is disabled, edits are applied when pressing enter, tabbing out, + clearing a field or deactivating due to a focus loss. + - The flag may be altered programmatically: + PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, false); // Disable for scalars + SliderInt(...); + PopItemFlag(); + PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for all + SliderInt(...); + PopItemFlag(); + But with upcoming new defaults it is expected you shouldn't touch them much. + - Both flags currently defaults to true, which matches previous behavior. + - The expectation is that for strings/text, enabling LiveEdit is a better default. + - The expectation is that for scalars, disable LiveEdit is a better default. + - The short-term intent is to change `ImGuiItemFlags_LiveEditOnInputScalar` to + default to being disabled, as soon as we get more feedback from users (SOON). + - We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the + default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not + expected to be a user preference but a programmer/widget preferences. + Also, we strive to make the toolkit consistent. - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. diff --git a/imgui.cpp b/imgui.cpp index 24aa23355..fc1d4d912 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6358,6 +6358,7 @@ bool ImGui::IsItemDeactivated() return g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount; } +// Since 1.92.9: consider using NoLiveEdit flags if all you need to that values are not written bo backing variable while typing. bool ImGui::IsItemDeactivatedAfterEdit() { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 672f24e66..dcd19a0ef 100644 --- a/imgui.h +++ b/imgui.h @@ -1238,16 +1238,29 @@ enum ImGuiChildFlags_ // (Those are shared by all submitted items) enum ImGuiItemFlags_ { - ImGuiItemFlags_None = 0, // (Default) + ImGuiItemFlags_None = 0, // Default: ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. - ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls). + ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing: keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls. ImGuiItemFlags_NoNavDefaultFocus = 1 << 2, // false // Disable item being a candidate for default focus (e.g. used by title bar items). ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held. ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). - ImGuiItemFlags_LiveEdit = 1 << 7, // true // WIP + //--------------------------------------------------------------------------------- + // LiveEdit refers to applying edits to backing variables _while_ typing. + // Widget: | Input: | w/ LiveEdit: | Output: + // InputText() | "123" | on(default) | "1" then "12" then "123" + // InputText() | "123" | off | "123" after validating or tabbing out or losing focus. + // DragFloat(), SliderInt(), etc. | "123" | on(default)* | 1 then 12 then 123 + // DragFloat(), SliderInt(), etc. | "123" | off* | 123 after validation or tabbing out or losing focus. + //--------------------------------------------------------------------------------- + // (*) The feature is currently being evaluated, and the aim is to change ImGuiItemFlags_LiveEditOnInputScalar to OFF by default. + // The legacy behavior until July 2026 was that LiveEdit was always ON for everything. + //--------------------------------------------------------------------------------- + ImGuiItemFlags_LiveEditOnInputText = 1 << 7, // true // InputText: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. + ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // true* // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. + ImGuiItemFlags_LiveEditOnInput = ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, }; // Flags for ImGui::InputText() @@ -1264,7 +1277,7 @@ enum ImGuiInputTextFlags_ // Inputs ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field - ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider disabling LiveEdit! or using IsItemDeactivatedAfterEdit() instead! ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode: validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). Note that Shift+Enter always enter a new line either way. @@ -1277,7 +1290,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). - //ImGuiInputTextFlags_NoLiveEdit = 1 << 25, + //ImGuiInputTextFlags_NoLiveEdit = 1 << 25, // Disable applying output to backing variable while typing. Same as setting ImGuiItemFlags_LiveEditOnInput to false. // Elide display / Alignment ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! @@ -1956,6 +1969,8 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, + //ImGuiSliderFlags_LiveEditOnInput = 1 << 13, // Shortcut to enable LiveEdit for this field. + //ImGuiSliderFlags_NoLiveEditOnInput= 1 << 14, // Shortcut to disable LiveEdit for this field. ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from legacy API (obsoleted 2020-08) that has got miscast to this enum, and will trigger an assert if needed. }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b1a326570..27413b7aa 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -86,6 +86,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsFonts() // [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsListBoxes() +// [SECTION] DemoWindowWidgetsLiveEdit() // [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsPlotting() // [SECTION] DemoWindowWidgetsProgressBars() @@ -331,6 +332,8 @@ struct ImGuiDemoWindowData // Other data bool DisableSections = false; + bool LiveEditOverride = false; + ImGuiItemFlags LiveEditFlags = ImGuiItemFlags_LiveEditOnInputText; ExampleTreeNode* DemoTree = NULL; ~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); } @@ -2009,6 +2012,44 @@ static void DemoWindowWidgetsListBoxes() } } +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgetsLiveEdit() +//----------------------------------------------------------------------------- + +static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) +{ + if (ImGui::TreeNode("Live Edit Flags")) + { + IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs"); + + ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing."); + + ImGui::Checkbox("Override Live Edit Flags in Demo Window", &demo_data->LiveEditOverride); + if (!demo_data->LiveEditOverride) + demo_data->LiveEditFlags = ImGui::GetItemFlags(); + + ImGui::BeginDisabled(demo_data->LiveEditOverride == false); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiItemFlags_LiveEditOnInputText", &demo_data->LiveEditFlags, ImGuiItemFlags_LiveEditOnInputText); + ImGui::CheckboxFlags("ImGuiItemFlags_LiveEditOnInputScalar", &demo_data->LiveEditFlags, ImGuiItemFlags_LiveEditOnInputScalar); + ImGui::Unindent(); + ImGui::EndDisabled(); + + ImGui::Text("Try typing '123' and seeing effect on backing value:"); + static char str[32] = ""; + ImGui::InputText("str", str, IM_COUNTOF(str)); + ImGui::Text("Backing value: \"%s\"", str); + static int i = 0; + ImGui::InputInt("int", &i, 0, 0); + ImGui::Text("Backing value: %d", i); + static float f = 0.0f; + ImGui::SliderFloat("float", &f, 0.0f, 100.0f); + ImGui::Text("Backing value: %f", f); + + ImGui::TreePop(); + } +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsMultiComponents() //----------------------------------------------------------------------------- @@ -2189,10 +2230,28 @@ static void DemoWindowWidgetsQueryingStatuses() }; static int item_type = 4; static bool item_disabled = false; + static bool liveedit_flags_override = false; + static ImGuiItemFlags liveedit_flags = 0; ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); + ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override); + ImGui::SameLine(); + if (!liveedit_flags_override) + liveedit_flags = ImGui::GetItemFlags(); + ImGui::BeginDisabled(liveedit_flags_override == false); + ImGui::CheckboxFlags("_LiveEditOnInput", &liveedit_flags, ImGuiItemFlags_LiveEditOnInput); + ImGui::SameLine(); + ImGui::CheckboxFlags("_LiveEditOnInputText", &liveedit_flags, ImGuiItemFlags_LiveEditOnInputText); + ImGui::SameLine(); + ImGui::CheckboxFlags("_LiveEditOnInputScalar", &liveedit_flags, ImGuiItemFlags_LiveEditOnInputScalar); + ImGui::EndDisabled(); + if (liveedit_flags_override) + { + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputText, (liveedit_flags & ImGuiItemFlags_LiveEditOnInputText) != 0); + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, (liveedit_flags & ImGuiItemFlags_LiveEditOnInputScalar) != 0); + } // Submit selected items so we can query their status in the code following it. bool ret = false; @@ -2279,6 +2338,11 @@ static void DemoWindowWidgetsQueryingStatuses() "IsItemHovered(_Tooltip) = %d", hovered_delay_none, hovered_delay_stationary, hovered_delay_short, hovered_delay_normal, hovered_delay_tooltip); + if (liveedit_flags_override) + { + ImGui::PopItemFlag(); + ImGui::PopItemFlag(); + } if (item_disabled) ImGui::EndDisabled(); @@ -4413,8 +4477,14 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) // IMGUI_DEMO_MARKER("Widgets"); const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom + const bool override_liveedit = demo_data->LiveEditOverride; if (disable_all) ImGui::BeginDisabled(); + if (override_liveedit) + { + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputText, (demo_data->LiveEditFlags & ImGuiItemFlags_LiveEditOnInputText) != 0); + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, (demo_data->LiveEditFlags & ImGuiItemFlags_LiveEditOnInputScalar) != 0); + } DemoWindowWidgetsBasic(); DemoWindowWidgetsBullets(); @@ -4434,6 +4504,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsFonts(); DemoWindowWidgetsImages(); DemoWindowWidgetsListBoxes(); + DemoWindowWidgetsLiveEdit(demo_data); DemoWindowWidgetsMultiComponents(); DemoWindowWidgetsPlotting(); DemoWindowWidgetsProgressBars(); @@ -4448,6 +4519,11 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsTreeNodes(); DemoWindowWidgetsVerticalSliders(); + if (override_liveedit) + { + ImGui::PopItemFlag(); + ImGui::PopItemFlag(); + } if (disable_all) ImGui::EndDisabled(); } diff --git a/imgui_internal.h b/imgui_internal.h index fdff662d0..0966819e6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() - ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEdit, // Please don't change, use PushItemFlag() instead. + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, // Please don't change, use PushItemFlag() instead. // Obsolete //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d63c5c5e0..efd3a64ff 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3763,6 +3763,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. + g.LastItemData.ItemFlags = (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (g.LastItemData.ItemFlags | ImGuiItemFlags_LiveEditOnInputText) : (g.LastItemData.ItemFlags & ~ImGuiItemFlags_LiveEditOnInputText); if (!TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags)) return false; @@ -3843,7 +3844,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data // Apply bool value_changed = false; - if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) { bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved. if (input_edited) @@ -4868,7 +4869,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnimReset(); // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) - if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) + if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) //-V560 InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below. // Take a copy of the initial buffer value. @@ -5410,7 +5411,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // FIXME-OPT: Could mark dirty state from the stb_textedit callbacks if (!is_readonly) { - if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified if (strcmp(state->TextSrc, buf) != 0) From 61a093635c1c26e85be3e4dca8b322533f552bde Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Thu, 16 Jul 2026 13:53:51 -0400 Subject: [PATCH 132/254] Backends: OpenGL3: fixed updating textures. (#9478, #9477, #9473, #8802) Fixed 0ff4a8a. --- backends/imgui_impl_opengl3.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index f1f2bc93c..ff9980035 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -670,7 +670,8 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) // Backup GL_UNPACK state that we modify, restore on exit. GLint last_unpack_row_length = 0; (void)last_unpack_row_length; GLint last_unpack_alignment = 0; (void)last_unpack_alignment; - if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + const bool last_unpack_state_save_and_restore = (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + if (last_unpack_state_save_and_restore) { #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES GL_CALL(glGetIntegerv(GL_UNPACK_ROW_LENGTH, &last_unpack_row_length)); @@ -744,7 +745,7 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) ImGui_ImplOpenGL3_DestroyTexture(tex); // Restore GL_UNPACK state - if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates) + if (last_unpack_state_save_and_restore) { #ifdef GL_UNPACK_ROW_LENGTH GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, last_unpack_row_length)); From 7038887c3278df0d63b4e7b4f399239ff1d7629d Mon Sep 17 00:00:00 2001 From: rbrtc <60761375+rbrtc@users.noreply.github.com> Date: Fri, 17 Jul 2026 06:51:28 -0500 Subject: [PATCH 133/254] Docs: fixed link in FAQ index. (#9480) --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index f81fb8b02..baf4daef3 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -25,7 +25,7 @@ or view this file with any Markdown viewer. | [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) | | [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | | **Q&A: Usage** | -| **[About the ID Stack system...](#q-about-the-id-stack-system)**
**[How can I have multiple widgets with the same label?](#q-how-can-i-have-multiple-widgets-with-the-same-label) (using `##` or `PushID()`)**
**[How can I have widgets with an empty label?](#q-how-can-i-have-widgets-with-an-empty-label) (using `##`)**
**[How can I animate the label of an existing widget?](#q-how-can-i-change-the-label-of-an-existing-widget) (using `###`)**
**[General description of the label and ID Stack system.](#general-description-of-the-label-and-id-stack-system)** | +| **[About the ID Stack system...](#q-about-the-id-stack-system)**
**[How can I have multiple widgets with the same label?](#q-how-can-i-have-multiple-widgets-with-the-same-label) (using `##` or `PushID()`)**
**[How can I have widgets with an empty label?](#q-how-can-i-have-widgets-with-an-empty-label) (using `##`)**
**[How can I make a label dynamic?](#q-how-can-i-make-a-label-dynamic) (using `###`)**
**[General description of the label and ID Stack system.](#general-description-of-the-label-and-id-stack-system)** | | [How can I display an image?](#q-how-can-i-display-an-image)
[What are ImTextureID/ImTextureRef?](#q-what-are-imtextureidimtextureref)| | [How can I use maths operators with ImVec2?](#q-how-can-i-use-maths-operators-with-imvec2) | | [How can I use my own maths types instead of ImVec2/ImVec4?](#q-how-can-i-use-my-own-maths-types-instead-of-imvec2imvec4) | From d00a87565bf2d05e8a0b2731c38a823b124fd7eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 20 Jul 2026 14:11:21 +0200 Subject: [PATCH 134/254] Fixed build when using IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#9482) Amend 6df50a0667 --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index fc1d4d912..32594498a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -11520,7 +11520,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu *p_highlight = g.FrameCount; *p_alive = g.FrameCount; if (*p_highlight >= g.FrameCount - 1) - window->DrawList->AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + window->DrawList->AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0f, 2.0f); } #endif } From e4f7c5c7223876c8796dfe5be5877cb5747fae0a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 20 Jul 2026 14:26:57 +0200 Subject: [PATCH 135/254] Disabling ImGuiItemFlags_LiveEditOnInputScalar by default! (#9476, #701) --- docs/CHANGELOG.txt | 39 +++++++++++++++++++++++---------------- imgui.cpp | 5 +++++ imgui.h | 14 ++++++-------- imgui_internal.h | 2 +- 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5cecbdb37..2c88d5706 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,12 @@ Breaking Changes: of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. - Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. +- DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now + defaulting to being disabled: inputting a value with the keyboard doesn't write + intermediate values to the backing variable. (#9476) + Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. + After: DragFloat() with user typing "123" --> write back 123 when validated/unfocused. + You can enable the flag back for a given scope if needed by specific widget/behavior. Other Changes: @@ -63,9 +69,13 @@ Other Changes: - Added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar` flags to configure the timing of applying edits of backing variables when typing values using a keyboard. - (#701, #9476, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, + (#9476, #701, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184, #5777, #6707, #6766, #8004, #8303, #8915, #9308) + - ImGuiItemFlags_LiveEditOnInputText is enabled by default (same as before): + - The expectation is that for strings/text, enabling LiveEdit is a better default. + - ImGuiItemFlags_LiveEditOnInputScalar is disabled by default (new behavior): + - The expectation is that for numeric/scalars values, disabling LiveEdit is a better default. - Until now: - Edits where always applied immediately to backing variable, which is equivalent to the `ImGuiItemFlags_LiveEditXXX` flags being enabled. @@ -76,28 +86,25 @@ Other Changes: Workarounds often required a backing store for scalar values, and there were also a few niggles related to `IsItemDeactivatedAfterEdit()` when using +/- buttons of an `InputInt()` widgets. - Many of those situations can now be naturally simplified by disabling - `ImGuiItemFlags_LiveEditOnInputScalar`, which is expected to become the default. + - Many of those situations can now be naturally simplified when disabling + `ImGuiItemFlags_LiveEditOnInputScalar` is disabled. - The new flags allows disabling this behavior selectively for strings fields such as `InputText()` vs scalar fields: `SliderInt()`, `InputFloat()`, etc. - When LiveEdit is disabled, edits are applied when pressing enter, tabbing out, clearing a field or deactivating due to a focus loss. - - The flag may be altered programmatically: - PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, false); // Disable for scalars + - The flag may be altered programmatically, e.g. for the entire frame scope: + NewFrame(); + PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, true); // Enable for scalars for the whole frame + .... + PopItemFlag(); + EndFrame(); + Or for a specific widget: + PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for one widget SliderInt(...); PopItemFlag(); - PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for all - SliderInt(...); - PopItemFlag(); - But with upcoming new defaults it is expected you shouldn't touch them much. - - Both flags currently defaults to true, which matches previous behavior. - - The expectation is that for strings/text, enabling LiveEdit is a better default. - - The expectation is that for scalars, disable LiveEdit is a better default. - - The short-term intent is to change `ImGuiItemFlags_LiveEditOnInputScalar` to - default to being disabled, as soon as we get more feedback from users (SOON). - We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the - default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not - expected to be a user preference but a programmer/widget preferences. + default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not expected + to be a user preference but a programmer/widget preferences. Also, we strive to make the toolkit consistent. - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) diff --git a/imgui.cpp b/imgui.cpp index 32594498a..ba8236e2b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,6 +395,11 @@ 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/07/20 (1.92.9) - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: + inputting a value with the keyboard doesn't write intermediate values to backing variable. (#9476) + - Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. + - After: DragFloat() with user typing "123" --> write back 123 when validated/unfocused. + You can enable the flag back for a given scope if needed by specific widget/behavior. - 2026/07/08 (1.92.9) - Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. - 2026/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. - 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. diff --git a/imgui.h b/imgui.h index dcd19a0ef..642fcb906 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.9 WIP" -#define IMGUI_VERSION_NUM 19286 +#define IMGUI_VERSION_NUM 19287 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1248,18 +1248,16 @@ enum ImGuiItemFlags_ ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). //--------------------------------------------------------------------------------- - // LiveEdit refers to applying edits to backing variables _while_ typing. + // LiveEdit refers to applying edits to backing variables _while_ typing a value using the keyboard. // Widget: | Input: | w/ LiveEdit: | Output: // InputText() | "123" | on(default) | "1" then "12" then "123" // InputText() | "123" | off | "123" after validating or tabbing out or losing focus. - // DragFloat(), SliderInt(), etc. | "123" | on(default)* | 1 then 12 then 123 - // DragFloat(), SliderInt(), etc. | "123" | off* | 123 after validation or tabbing out or losing focus. - //--------------------------------------------------------------------------------- - // (*) The feature is currently being evaluated, and the aim is to change ImGuiItemFlags_LiveEditOnInputScalar to OFF by default. - // The legacy behavior until July 2026 was that LiveEdit was always ON for everything. + // DragFloat(), SliderInt(), etc. | "123" | on* | 1 then 12 then 123 + // DragFloat(), SliderInt(), etc. | "123" | off(default)* | 123 after validation or tabbing out or losing focus. + // (*) Since 1.92.9 (July 2026), ImGuiItemFlags_LiveEditOnInputScalar is OFF by default. In prior version it was ON for everything. //--------------------------------------------------------------------------------- ImGuiItemFlags_LiveEditOnInputText = 1 << 7, // true // InputText: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. - ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // true* // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. + ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // false // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInput = ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, }; diff --git a/imgui_internal.h b/imgui_internal.h index 0966819e6..7eb5780f2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() - ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, // Please don't change, use PushItemFlag() instead. + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEditOnInputText, // Please don't change, use PushItemFlag() instead. // Obsolete //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior From c9c458c40a47d434464eea36f124744af4d672c6 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 20 Jul 2026 15:20:27 +0200 Subject: [PATCH 136/254] Update FAQ.md --- docs/FAQ.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index baf4daef3..8f5bd755a 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -85,14 +85,15 @@ Here's a very simplified comparison between the approach taken by Dear ImGui vs |--------------------------|--------------------------| | UI fully issued on every update. | UI issued once then later modified. | | UI layout is fully dynamic and can change at any time.
UI is generally emitted programmatically, which empowers reflecting a dynamic set of data. | UI layout is mostly static.
UI may be emitted programmatically or from data created by offline tools. UI need extra code to evolve, which is often tedious and error-prone if it needs to be reflecting dynamic data and systems. | -| Application can submit UI based on arbitrary logic and then forget about it. | Application needs more bookkeeping of UI elements. | +| UI system is optimized for the worst case and designed be optimal under constant changes. | UI system is optimized for the case where nothing changes. Performances tends to degrade when UI changes. | +| Application can submit UI based on arbitrary logic without bookkeeping all aspects of that logic. | Application needs more bookkeeping of UI elements. | | UI library stores minimal amounts of data. At one point in time, it typically doesn't know or remember which other widgets are displayed and which widgets are coming next. As a result, certain layout features (alignment, resizing) are not as easy to implement or require ad-hoc code. | UI library stores entire widgets tree and state. UI library can use this retained data to easily layout things. | | UI code may be added anywhere.
You can even create UI to edit a local variable! | UI code needs to be added in dedicated spots. | | UI layout/logic/action/data bindings are all nearby in the code. | UI layout/logic/action/data bindings in distinct functions, files or formats. | | Data is naturally always synchronized. | Use callback/signal/slot for synchronizing data (error-prone). | -| API is simple and easy to learn. In particular, doing basic things is very easy. | API is more complex and specialized. | -| API is low-level (raw language types). | API are higher-level (more abstractions, advanced language features). | -| Less fancy look and feel. | Standard look and feel. | +| API is simple and easy to learn. In particular, doing basic things is very easy.
Attractive to more programmers. | API is more complex and specialized.
Tends to require specialized programmers. | +| API is low-level. Few abstractions, uses raw language types and your existing data. | API are higher-level. More abstractions, advanced language features. | +| Less fancy look and feel. But keeps improving :) | Standard look and feel. | | Compile yourself. Easy to debug, hack, modify, study. | Mostly use precompiled libraries. Compiling, modifying or studying is daunting if not impossible. | | Run on every platform. | Run on limited desktop platforms. | From 295385ff9618cc728175c308ca7b1a770faf43e9 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 20 Jul 2026 15:23:35 +0200 Subject: [PATCH 137/254] Update FAQ.md --- docs/FAQ.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 8f5bd755a..e805566d8 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -85,7 +85,7 @@ Here's a very simplified comparison between the approach taken by Dear ImGui vs |--------------------------|--------------------------| | UI fully issued on every update. | UI issued once then later modified. | | UI layout is fully dynamic and can change at any time.
UI is generally emitted programmatically, which empowers reflecting a dynamic set of data. | UI layout is mostly static.
UI may be emitted programmatically or from data created by offline tools. UI need extra code to evolve, which is often tedious and error-prone if it needs to be reflecting dynamic data and systems. | -| UI system is optimized for the worst case and designed be optimal under constant changes. | UI system is optimized for the case where nothing changes. Performances tends to degrade when UI changes. | +| UI system is optimized for the worst case and designed to be optimal even under frequent changes. | UI system is optimized for the case where nothing changes. Performances tends to degrade when anything changes. | | Application can submit UI based on arbitrary logic without bookkeeping all aspects of that logic. | Application needs more bookkeeping of UI elements. | | UI library stores minimal amounts of data. At one point in time, it typically doesn't know or remember which other widgets are displayed and which widgets are coming next. As a result, certain layout features (alignment, resizing) are not as easy to implement or require ad-hoc code. | UI library stores entire widgets tree and state. UI library can use this retained data to easily layout things. | | UI code may be added anywhere.
You can even create UI to edit a local variable! | UI code needs to be added in dedicated spots. | @@ -95,7 +95,7 @@ Here's a very simplified comparison between the approach taken by Dear ImGui vs | API is low-level. Few abstractions, uses raw language types and your existing data. | API are higher-level. More abstractions, advanced language features. | | Less fancy look and feel. But keeps improving :) | Standard look and feel. | | Compile yourself. Easy to debug, hack, modify, study. | Mostly use precompiled libraries. Compiling, modifying or studying is daunting if not impossible. | -| Run on every platform. | Run on limited desktop platforms. | +| Run on every platform (incl. web, consoles, mobiles, legacy systems). | Run on major desktop platforms. | Idiomatic Dear ImGui code: ```cpp From 548389359c4b2edfcc3b7d48d8750e324ebdc82a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 20 Jul 2026 16:42:07 +0200 Subject: [PATCH 138/254] (Breaking) ImDrawData: marked draw_data->CmdListsCount as obsolete. Use draw_data->CmdLists.Size. # Conflicts: # docs/CHANGELOG.txt # imgui.cpp --- docs/CHANGELOG.txt | 9 ++++++--- imgui.cpp | 12 +++++++++--- imgui.h | 5 ++++- imgui_draw.cpp | 10 +++++++--- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2c88d5706..0dec2215e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -53,7 +53,8 @@ Breaking Changes: Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. After: DragFloat() with user typing "123" --> write back 123 when validated/unfocused. You can enable the flag back for a given scope if needed by specific widget/behavior. - +- ImDrawData: marked `CmdListsCount` as obsolete (technically was obsoleted in 1.89.8). + Before: `draw_data->CmdListsCount`, After: `draw_data->CmdLists.Size`. Other Changes: @@ -3541,8 +3542,10 @@ Breaking changes: input queue. This is automatically cleared by io.ClearInputKeys(). (#4921) - ImDrawData: CmdLists[] array is now owned, changed from 'ImDrawList**' to 'ImVector'. Majority of users shouldn't be affected, but you - cannot compare to NULL nor reassign manually anymore. - Instead use AddDrawList(). Allocation count are identical. (#6406, #4879, #1878) + cannot compare to NULL nor reassign manually anymore. (#6406, #4879, #1878) + - Allocation count are identical. + - Use `draw_data->CmdLists.Size` instead of `draw_list->CmdListsCount`! + - Use `AddDrawList()` to add to a `ImDrawData`. Other changes: diff --git a/imgui.cpp b/imgui.cpp index ba8236e2b..2e97af26b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -400,6 +400,9 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. - After: DragFloat() with user typing "123" --> write back 123 when validated/unfocused. You can enable the flag back for a given scope if needed by specific widget/behavior. + - 2026/07/20 (1.92.9) - ImDrawData: marked ImDrawData::CmdListsCount as obsolete (technically was obsoleted in 1.89.8). + - Before: draw_data->CmdListsCount + - After: draw_data->CmdLists.Size - 2026/07/08 (1.92.9) - Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. - 2026/07/06 (1.92.9) - ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. - 2026/06/02 (1.92.9) - TreeNode: commented out legacy name ImGuiTreeNodeFlags_SpanTextWidth which was obsoleted in 1.90.7 (May 2024). Use ImGuiTreeNodeFlags_SpanLabelWidth instead. @@ -736,7 +739,9 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - ImDrawCornerFlags_XXX -> use ImDrawFlags_RoundCornersXXX flags. Read 1.82 Changelog for details + grep commented names in sources. - commented out runtime support for hardcoded ~0 or 0x01..0x0F rounding flags values for AddRect()/AddRectFilled()/PathRect()/AddImageRounded() -> use ImDrawFlags_RoundCornersXXX flags. Read 1.82 Changelog for details - 2023/08/25 (1.89.9) - clipper: Renamed IncludeRangeByIndices() (also called ForceDisplayRangeByIndices() before 1.89.6) to IncludeItemsByIndex(). Kept inline redirection function. Sorry! - - 2023/07/12 (1.89.8) - ImDrawData: CmdLists now owned, changed from ImDrawList** to ImVector. Majority of users shouldn't be affected, but you cannot compare to NULL nor reassign manually anymore. Instead use AddDrawList(). (#6406, #4879, #1878) + - 2023/07/12 (1.89.8) - ImDrawData: CmdLists now owned, changed from ImDrawList** to ImVector. Majority of users shouldn't be affected, but you cannot compare to NULL nor reassign manually anymore. (#6406, #4879, #1878) + - use `draw_data->CmdLists.Size` instead of `draw_list->CmdListsCount`. + - use `draw_data->AddDrawList()` to add to a ImDrawData. - 2023/06/28 (1.89.7) - overlapping items: obsoleted 'SetItemAllowOverlap()' (called after item) in favor of calling 'SetNextItemAllowOverlap()' (called before item). 'SetItemAllowOverlap()' didn't and couldn't work reliably since 1.89 (2022-11-15). - 2023/06/28 (1.89.7) - overlapping items: renamed 'ImGuiTreeNodeFlags_AllowItemOverlap' to 'ImGuiTreeNodeFlags_AllowOverlap', 'ImGuiSelectableFlags_AllowItemOverlap' to 'ImGuiSelectableFlags_AllowOverlap'. Kept redirecting enums (will obsolete). - 2023/06/28 (1.89.7) - overlapping items: IsItemHovered() now by default return false when querying an item using AllowOverlap mode which is being overlapped. Use ImGuiHoveredFlags_AllowWhenOverlappedByItem to revert to old behavior. @@ -5958,13 +5963,15 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) viewport->DrawDataBuilder.Layers[1]->resize(0); draw_data->Valid = true; - draw_data->CmdListsCount = 0; draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; draw_data->DisplayPos = viewport->Pos; draw_data->DisplaySize = viewport->Size; draw_data->FramebufferScale = io.DisplayFramebufferScale; draw_data->OwnerViewport = viewport; draw_data->Textures = &ImGui::GetPlatformIO().Textures; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + draw_data->CmdListsCount = 0; +#endif } // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. @@ -6232,7 +6239,6 @@ void ImGui::Render() // We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there (especially in docking branch). ImDrawData* draw_data = &viewport->DrawDataP; - IM_ASSERT(draw_data->CmdLists.Size == draw_data->CmdListsCount); for (ImDrawList* draw_list : draw_data->CmdLists) draw_list->_PopUnusedDrawCmd(); diff --git a/imgui.h b/imgui.h index 642fcb906..d219d5a6d 100644 --- a/imgui.h +++ b/imgui.h @@ -3493,7 +3493,6 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int CmdListsCount; // == CmdLists.Size. (OBSOLETE: exists for legacy reasons). Number of ImDrawList* to render. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. @@ -3503,6 +3502,10 @@ struct ImDrawData ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overridden or set to NULL if you want to manually update textures. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + int CmdListsCount; // Use CmdLists.Size instead. Number of ImDrawList* to render. +#endif + // Functions ImDrawData() { Clear(); } IMGUI_API void Clear(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4a68ff4a8..d95dc20ad 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2280,11 +2280,14 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) void ImDrawData::Clear() { Valid = false; - CmdListsCount = TotalIdxCount = TotalVtxCount = 0; + TotalIdxCount = TotalVtxCount = 0; CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them. DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f); OwnerViewport = NULL; Textures = NULL; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + CmdListsCount = 0; +#endif } // Important: 'out_list' is generally going to be draw_data->CmdLists, but may be another temporary list @@ -2329,16 +2332,17 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector // Add to output list + records state in ImDrawData out_list->push_back(draw_list); - draw_data->CmdListsCount++; draw_data->TotalVtxCount += draw_list->VtxBuffer.Size; draw_data->TotalIdxCount += draw_list->IdxBuffer.Size; } void ImDrawData::AddDrawList(ImDrawList* draw_list) { - IM_ASSERT(CmdLists.Size == CmdListsCount); draw_list->_PopUnusedDrawCmd(); ImGui::AddDrawListToDrawDataEx(this, &CmdLists, draw_list); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + CmdListsCount = CmdLists.Size; +#endif } // For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! From 6f41d13c7a93cf65aca4afe9bf24b610740c3d5a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 20 Jul 2026 16:57:17 +0200 Subject: [PATCH 139/254] DrawData: added FrameCount field to facilitate debugging multi-threading issues. (#8597, #9136) --- imgui.cpp | 7 ++++--- imgui.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2e97af26b..741d6b2a7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5954,7 +5954,7 @@ static void FlattenDrawDataIntoSingleLayer(ImDrawDataBuilder* builder) static void InitViewportDrawData(ImGuiViewportP* viewport) { - ImGuiIO& io = ImGui::GetIO(); + ImGuiContext& g = *GImGui; ImDrawData* draw_data = &viewport->DrawDataP; viewport->DrawDataBuilder.Layers[0] = &draw_data->CmdLists; @@ -5963,12 +5963,13 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) viewport->DrawDataBuilder.Layers[1]->resize(0); draw_data->Valid = true; + draw_data->FrameCount = g.FrameCount; draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; draw_data->DisplayPos = viewport->Pos; draw_data->DisplaySize = viewport->Size; - draw_data->FramebufferScale = io.DisplayFramebufferScale; + draw_data->FramebufferScale = g.IO.DisplayFramebufferScale; draw_data->OwnerViewport = viewport; - draw_data->Textures = &ImGui::GetPlatformIO().Textures; + draw_data->Textures = &g.PlatformIO.Textures; #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS draw_data->CmdListsCount = 0; #endif diff --git a/imgui.h b/imgui.h index d219d5a6d..155f1093b 100644 --- a/imgui.h +++ b/imgui.h @@ -3493,6 +3493,7 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. + int FrameCount; // Frame counter of the emitter context. For debugging purpose. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. From b335bf4328054498219afb4188ec39912f2a95b1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 20 Jul 2026 21:16:58 +0200 Subject: [PATCH 140/254] Added ImTextureData::QueueUserData for staged-rendering texture queues e.g. imgui_threaded_rendering.h. (#8597, #9136) --- imgui.h | 3 ++- imgui_draw.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 155f1093b..6b7389753 100644 --- a/imgui.h +++ b/imgui.h @@ -3558,13 +3558,14 @@ struct ImTextureRect // Why does we store two identifiers: TexID and BackendUserData? // - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. // - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. - // In columns below: who reads/writes each fields? 'r'=read, 'w'=write, 'core'=main library, 'backend'=renderer backend +// In columns below: who reads/writes each fields? 'r'=read, 'w'=write, 'core'=main library, 'backend'=renderer backend struct ImTextureData { //------------------------------------------ core / backend --------------------------------------- int UniqueID; // w - // [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. ImTextureStatus Status; // rw rw // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! void* BackendUserData; // - rw // Convenience storage for backend. Some backends may have enough with TexID. + void* QueueUserData; // r - // Convenience storage for a staged/multi-threaded rendering texture queue (e.g. imgui_threaded_rendering.h. See #8597). When != NULL, core assumes the texture is referenced by the queue. ImTextureID TexID; // r w // Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function. ImTextureFormat Format; // w r // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 int Width; // w r // Texture width diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d95dc20ad..8598d7026 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2866,13 +2866,14 @@ bool ImTextureDataUpdateNewFrame(ImTextureData* tex) // If a texture has never reached the backend, they don't need to know about it. // (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy // when invalidating graphics objects twice, which would previously remove it from the list and crash.) - if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && tex->QueueUserData == NULL) tex->Status = ImTextureStatus_Destroyed; // Process texture being destroyed if (tex->Status == ImTextureStatus_Destroyed) { IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); + IM_ASSERT(tex->QueueUserData == NULL && "Texture queue set Status to Destroyed but did not clear QueueUserData!"); if (tex->WantDestroyNextFrame) remove_from_list = true; // Destroy was scheduled by us else From f594633b541cf52f6e5746f99b83fc56ab06e733 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 23 Jul 2026 17:49:39 +0200 Subject: [PATCH 141/254] Popups: added bool return value to OpenPopup(), OpenPopupOnItemClick() functions. (#9429) Following on 795cf6fcb5. --- docs/CHANGELOG.txt | 5 ++++- imgui.cpp | 30 ++++++++++++++++++------------ imgui.h | 10 ++++++---- imgui_internal.h | 2 +- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0dec2215e..0074103db 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -107,6 +107,9 @@ Other Changes: default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not expected to be a user preference but a programmer/widget preferences. Also, we strive to make the toolkit consistent. +- Popups: + - Added bool return value to `OpenPopup()`, `OpenPopupOnItemClick()` functions + to notify when the popup was just opened. (#9429) - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) This is automatically scaled by `style.ScaleAllSizes()`. @@ -131,7 +134,7 @@ Other Changes: - Headers: fixed label being clipped early to reserve space for a sort marker even when no sort marker is displayed. Auto-fitting a column still accounts for the possible marker, so that sorting after an auto-fit doesn't clip the label. -- Multi-Select +- Multi-Select: - Reworked ImGuiMultiSelectFlags_NoAutoSelect as it carried side-effects that were hardcoded/designed to use multi-selection on checkboxes. (#9391) Specifically removed those undocumented behaviors from _NoAutoSelect: diff --git a/imgui.cpp b/imgui.cpp index 741d6b2a7..b93e48a66 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12430,24 +12430,26 @@ ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) return NULL; } -void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) +bool ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiID id = g.CurrentWindow->GetID(str_id); IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X)\n", str_id, id); - OpenPopupEx(id, popup_flags); + return OpenPopupEx(id, popup_flags); } -void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags) +bool ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags) { - OpenPopupEx(id, popup_flags); + return OpenPopupEx(id, popup_flags); } // Mark popup as open (toggle toward open state). -// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. -// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). -// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) -void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) +// - Return true when the popup is toggled open, which allows you to capture local state if needed. +// You may also call IsWindowAppearing() inside the later BeginPopup() scope if you need to prepare/compute data for the popup. +// - Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. +// - Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). +// - One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL). +bool ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* parent_window = g.CurrentWindow; @@ -12455,7 +12457,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup) if (IsPopupOpen((ImGuiID)0, ImGuiPopupFlags_AnyPopupId)) - return; + return false; ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. popup_ref.PopupId = id; @@ -12470,6 +12472,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) if (g.OpenPopupStack.Size < current_stack_size + 1) { g.OpenPopupStack.push_back(popup_ref); + return true; } else { @@ -12497,6 +12500,8 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) // This is equivalent to what ClosePopupToLevel() does. //if (g.OpenPopupStack[current_stack_size].PopupId == id) // FocusWindow(parent_window); + + return !keep_existing; } } @@ -12781,16 +12786,17 @@ bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags) // Helper to open a popup if mouse button is released over the item // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() -void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) +bool ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; if (IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID)) { + ImGuiWindow* window = g.CurrentWindow; ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) - OpenPopupEx(id, popup_flags); + return OpenPopupEx(id, popup_flags); } + return false; } // This is a helper to handle the simplest case of associating one named popup to one given widget. diff --git a/imgui.h b/imgui.h index 6b7389753..5ef2e61a5 100644 --- a/imgui.h +++ b/imgui.h @@ -850,15 +850,17 @@ namespace ImGui IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! // Popups: open/close functions - // - OpenPopup(): set popup state to open. ImGuiPopupFlags are available for opening options. + // - OpenPopup(): set popup state to open (unless one of the specified ImGuiPopupFlags prevent opening). + // - OpenPopupXXX() functions return true when the popup is toggled open, which allows you to capture local state if needed. + // You may also call IsWindowAppearing() inside the later BeginPopup() scope if you need to prepare/compute data for the popup. // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. // - CloseCurrentPopup(): use inside the BeginPopup()/EndPopup() scope to close manually. // - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). // - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup(). // - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened. - IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). - IMGUI_API void OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks - IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) + IMGUI_API bool OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). + IMGUI_API bool OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 0); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. // Popups: Open+Begin popup combined functions helpers to create context menus. diff --git a/imgui_internal.h b/imgui_internal.h index 7eb5780f2..584914fc4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3504,7 +3504,7 @@ namespace ImGui // Popups, Modals IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); IMGUI_API bool BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags); - IMGUI_API void OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); + IMGUI_API bool OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsExceptModals(); From f31f98f4cda8552531d9a65ef170b527c32bd51f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 23 Jul 2026 17:55:43 +0200 Subject: [PATCH 142/254] ImDrawData: fixed late initialized field. (Amend 6f41d13) Would effectively be initialized on use but best to clear on constructor. --- imgui.h | 2 +- imgui_draw.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 5ef2e61a5..918d253d2 100644 --- a/imgui.h +++ b/imgui.h @@ -3495,7 +3495,7 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int FrameCount; // Frame counter of the emitter context. For debugging purpose. + int FrameCount; // Frame counter of the emitter context. Mostly for debugging purpose. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8598d7026..6ec7478b9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2280,7 +2280,7 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) void ImDrawData::Clear() { Valid = false; - TotalIdxCount = TotalVtxCount = 0; + FrameCount = TotalIdxCount = TotalVtxCount = 0; CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them. DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f); OwnerViewport = NULL; From d94d0f3649c99d9a026518214f796aaacde707fe Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 23 Jul 2026 18:56:53 +0200 Subject: [PATCH 143/254] Examples: SDL+WebGPU: removed stray glfw from cmakefiles. (#9387, #9428) --- examples/example_sdl2_wgpu/CMakeLists.txt | 2 +- examples/example_sdl3_wgpu/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example_sdl2_wgpu/CMakeLists.txt b/examples/example_sdl2_wgpu/CMakeLists.txt index 8795070c3..aff4a184f 100644 --- a/examples/example_sdl2_wgpu/CMakeLists.txt +++ b/examples/example_sdl2_wgpu/CMakeLists.txt @@ -162,7 +162,7 @@ else() # Native/Desktop build endif() list(APPEND WGVK_PLATFORM_LIBS m dl pthread) endif() - set(LIBRARIES glfw Vulkan::Vulkan ${WGVK_PLATFORM_LIBS}) + set(LIBRARIES Vulkan::Vulkan ${WGVK_PLATFORM_LIBS}) endif() endif() diff --git a/examples/example_sdl3_wgpu/CMakeLists.txt b/examples/example_sdl3_wgpu/CMakeLists.txt index 21a72bd0e..b7b05881d 100644 --- a/examples/example_sdl3_wgpu/CMakeLists.txt +++ b/examples/example_sdl3_wgpu/CMakeLists.txt @@ -162,7 +162,7 @@ else() # Native/Desktop build endif() list(APPEND WGVK_PLATFORM_LIBS m dl pthread) endif() - set(LIBRARIES glfw Vulkan::Vulkan ${WGVK_PLATFORM_LIBS}) + set(LIBRARIES Vulkan::Vulkan ${WGVK_PLATFORM_LIBS}) endif() endif() From e7221655615f38bedd6651554edc75975aa0d1fb Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 25 Jul 2026 12:21:17 +0200 Subject: [PATCH 144/254] Docs: rework FAQ index, added multi-threading and string-view details. --- docs/BACKENDS.md | 2 +- docs/FAQ.md | 105 +++++++++++++++++++++++++++++++---------------- imgui.cpp | 5 ++- 3 files changed, 74 insertions(+), 38 deletions(-) diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 2fb9cfa71..2f3a802d8 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -117,7 +117,7 @@ If you are not sure which backend to use, the recommended platform/frameworks fo | GLFW | https://github.com/glfw/glfw | imgui_impl_glfw.cpp | | | Sokol | https://github.com/floooh/sokol | [util/sokol_imgui.h](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h) | Lower-level than GLFW/SDL | -If your application runs on Windows or if you are using multi-viewport, the win32 backend handles some details a little better than other backends. +If your application runs on Windows or if you are using multi-viewports, the imgui_impl_win32 backend handles some details better than other backends. ## Using third-party Backends diff --git a/docs/FAQ.md b/docs/FAQ.md index e805566d8..d67616725 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -9,42 +9,58 @@ or view this file with any Markdown viewer. ## Index -| **Q&A: Basics** | -:---------------------------------------------------------- | -| [Where is the documentation?](#q-where-is-the-documentation) | -| [What is this library called?](#q-what-is-this-library-called) | -| [What is the difference between Dear ImGui and traditional UI toolkits?](#q-what-is-the-difference-between-dear-imgui-and-traditional-ui-toolkits) | -| [Which version should I get?](#q-which-version-should-i-get) | -| **Q&A: Integration** | -| **[How to get started?](#q-how-to-get-started)** | -| **[How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?](#q-how-can-i-tell-whether-to-dispatch-mousekeyboard-to-dear-imgui-or-my-application)** | -| [How can I enable keyboard or gamepad controls?](#q-how-can-i-enable-keyboard-or-gamepad-controls) | -| [How can I use this on a machine without mouse, keyboard or screen? (input share, remote display)](#q-how-can-i-use-this-on-a-machine-without-mouse-keyboard-or-screen-input-share-remote-display) | -| [How can I create my own backend?](#q-how-can-i-create-my-own-backend) -| [I integrated Dear ImGui in my engine and little squares are showing instead of text...](#q-i-integrated-dear-imgui-in-my-engine-and-little-squares-are-showing-instead-of-text) | -| [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) | -| [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) | -| **Q&A: Usage** | -| **[About the ID Stack system...](#q-about-the-id-stack-system)**
**[How can I have multiple widgets with the same label?](#q-how-can-i-have-multiple-widgets-with-the-same-label) (using `##` or `PushID()`)**
**[How can I have widgets with an empty label?](#q-how-can-i-have-widgets-with-an-empty-label) (using `##`)**
**[How can I make a label dynamic?](#q-how-can-i-make-a-label-dynamic) (using `###`)**
**[General description of the label and ID Stack system.](#general-description-of-the-label-and-id-stack-system)** | -| [How can I display an image?](#q-how-can-i-display-an-image)
[What are ImTextureID/ImTextureRef?](#q-what-are-imtextureidimtextureref)| -| [How can I use maths operators with ImVec2?](#q-how-can-i-use-maths-operators-with-imvec2) | -| [How can I use my own maths types instead of ImVec2/ImVec4?](#q-how-can-i-use-my-own-maths-types-instead-of-imvec2imvec4) | -| [How can I interact with standard C++ types (such as std::string and std::vector)?](#q-how-can-i-interact-with-standard-c-types-such-as-stdstring-and-stdvector) | -| [How can I display custom shapes? (using low-level ImDrawList API)](#q-how-can-i-display-custom-shapes-using-low-level-imdrawlist-api) | -| **Q&A: Fonts, Text** | -| [How should I handle DPI in my application?](#q-how-should-i-handle-dpi-in-my-application) | -| [How can I load a different font than the default?](#q-how-can-i-load-a-different-font-than-the-default) | -| [How can I easily use icons in my application?](#q-how-can-i-easily-use-icons-in-my-application) | -| [How can I load multiple fonts?](#q-how-can-i-load-multiple-fonts) | -| [How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?](#q-how-can-i-display-and-input-non-latin-characters-such-as-chinese-japanese-korean-cyrillic) | -| **Q&A: Concerns** | -| [Who uses Dear ImGui?](#q-who-uses-dear-imgui) | -| [Can you create elaborate/serious tools with Dear ImGui?](#q-can-you-create-elaborateserious-tools-with-dear-imgui) | -| [Can you reskin the look of Dear ImGui?](#q-can-you-reskin-the-look-of-dear-imgui) | -| [Why using C++ (as opposed to C)?](#q-why-using-c-as-opposed-to-c) | -| **Q&A: Community** | -| [How can I help?](#q-how-can-i-help) | +### Basics +- [Where is the documentation?](#q-where-is-the-documentation) +- [What is this library called?](#q-what-is-this-library-called) +- [What is the difference between Dear ImGui and traditional UI toolkits?](#q-what-is-the-difference-between-dear-imgui-and-traditional-ui-toolkits) +- [Which version should I get?](#q-which-version-should-i-get) + +### Integration + +- **[How to get started?](#q-how-to-get-started)** +- **[How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?](#q-how-can-i-tell-whether-to-dispatch-mousekeyboard-to-dear-imgui-or-my-application)** +- [How can I enable keyboard or gamepad controls?](#q-how-can-i-enable-keyboard-or-gamepad-controls) +- [How can I use this on a machine without mouse, keyboard or screen? (input share, remote display)](#q-how-can-i-use-this-on-a-machine-without-mouse-keyboard-or-screen-input-share-remote-display) +- [How can I create my own backend?](#q-how-can-i-create-my-own-backend) +- [I integrated Dear ImGui in my engine and little squares are showing instead of text...](#q-i-integrated-dear-imgui-in-my-engine-and-little-squares-are-showing-instead-of-text) +- [I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-clipping-or-disappearing-when-i-move-windows-around) +- [I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...](#q-i-integrated-dear-imgui-in-my-engine-and-some-elements-are-displaying-outside-their-expected-windows-boundaries) + +### Usage + +- **[About the ID Stack system...](#q-about-the-id-stack-system)** + - **[How can I have multiple widgets with the same label?](#q-how-can-i-have-multiple-widgets-with-the-same-label) (using `##` or `PushID()`)** + - **[How can I have widgets with an empty label?](#q-how-can-i-have-widgets-with-an-empty-label) (using `##`)** + - **[How can I make a label dynamic?](#q-how-can-i-make-a-label-dynamic) (using `###`)** + - **[General description of the label and ID Stack system.](#general-description-of-the-label-and-id-stack-system)** +- [How can I display an image?](#q-how-can-i-display-an-image), [What are ImTextureID/ImTextureRef?](#q-what-are-imtextureidimtextureref) +- [How can I use maths operators with ImVec2?](#q-how-can-i-use-maths-operators-with-imvec2) +- [How can I use my own maths types instead of ImVec2/ImVec4?](#q-how-can-i-use-my-own-maths-types-instead-of-imvec2imvec4) +- [How can I interact with standard C++ types (such as std::string and std::vector)?](#q-how-can-i-interact-with-standard-c-types-such-as-stdstring-and-stdvector) +- [How can I display custom shapes? (using low-level ImDrawList API)](#q-how-can-i-display-custom-shapes-using-low-level-imdrawlist-api) +- [About Multi-Threading](#about-multi-threading) + +### Fonts, Text + +- [How should I handle DPI in my application?](#q-how-should-i-handle-dpi-in-my-application) +- [How can I load a different font than the default?](#q-how-can-i-load-a-different-font-than-the-default) +- [How can I easily use icons in my application?](#q-how-can-i-easily-use-icons-in-my-application) +- [How can I load multiple fonts?](#q-how-can-i-load-multiple-fonts) +- [How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?](#q-how-can-i-display-and-input-non-latin-characters-such-as-chinese-japanese-korean-cyrillic) + +### Concerns + +- [Who uses Dear ImGui?](#q-who-uses-dear-imgui) +- [Can you create elaborate/serious tools with Dear ImGui?](#q-can-you-create-elaborateserious-tools-with-dear-imgui) +- [Can you reskin the look of Dear ImGui?](#q-can-you-reskin-the-look-of-dear-imgui) +- [Why using C++ (as opposed to C)?](#q-why-using-c-as-opposed-to-c) + +### Community + +- [How can I help?](#q-how-can-i-help) + +-------- # Q&A: Basics @@ -667,6 +683,7 @@ This way you will be able to use your own types everywhere, e.g. passing `MyVect --- ### Q: How can I interact with standard C++ types (such as std::string and std::vector)? + - Being highly portable (backends/bindings for several languages, frameworks, programming styles, obscure or older platforms/compilers), and aiming for compatibility & performance suitable for every modern real-time game engine, Dear ImGui does not use any of std C++ types. We use raw types (e.g. char* instead of std::string) because they adapt to more use cases. - To use ImGui::InputText() with a std::string or any resizable string class, see [misc/cpp/imgui_stdlib.h](https://github.com/ocornut/imgui/blob/master/misc/cpp/imgui_stdlib.h). - To use combo boxes and list boxes with `std::vector` or any other data structure: the `BeginCombo()/EndCombo()` API @@ -686,6 +703,10 @@ One possible implementation of a helper to facilitate printf-style building of s This is a small helper where you can instance strings with configurable local buffers length. Many game engines will provide similar or better string helpers. +Using string views: + +- String view types such as `std::string_view` may be used with the [feature/string_view](https://github.com/ocornut/imgui/tree/features/string_view) branch. This branch is actively maintained and supported. Please provide feedback if you use it. We are waiting for a good moment to merge it. + ##### [Return to Index](#index) --- @@ -726,6 +747,20 @@ ImGui::End(); --- +### About Multi-Threading + +A same Dear ImGui context may be not used from multiple threads in parallel. + +If you want to use a same context within parallel tasks for occasional debug purpose, consider using a lock. + +If you want to submit contents from a main/update thread but render Dear ImGui output in a dedicated render thread, you'll need to stage `ImDrawData` and texture requests. See the `ImDrawDataSnapshot` and `ImTextureQueue` helpers in [imgui_threaded_rendering](https://github.com/ocornut/imgui_club#imgui_threaded_rendering). Also see topics with [label: multi-threading](https://github.com/ocornut/imgui/issues?q=label%3Amulti-threading). + +If you use multiple Dear ImGui contexts and want to use them from multiple threads, you need to `#define GImGui` to become a TLS variable (see details near the definition of `GImGui`. If you need to display multiple contexts simultaneously, consider using [imgui_multicontext_compositor](https://github.com/ocornut/imgui_club#imgui_multicontext_compositor). Also see topics with [label: multi-contexts](https://github.com/ocornut/imgui/issues?q=label%3Amulti-contexts). + +There is a script/patch to make the API take an explicit context pointer: [#5856](https://github.com/ocornut/imgui/pull/5856). It is presently unmaintained and likely to easy to update. It has been decided that the change is not currently worth applying to main-line but maybe will be in a future version. + +--- + # Q&A: Fonts, Text ### Q: How should I handle DPI in my application? diff --git a/imgui.cpp b/imgui.cpp index b93e48a66..2eab3aae3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -32,7 +32,7 @@ // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. // Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring contracts. -// PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Funding +// PLEASE reach out at omar AT discohello DOT com. See https://github.com/ocornut/imgui/wiki/Funding // Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. @@ -1198,6 +1198,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: Q: How can I use my own math types instead of ImVec2? Q: How can I interact with standard C++ types (such as std::string and std::vector)? Q: How can I display custom shapes? (using low-level ImDrawList API) + Q: About Multi-Threading >> See https://www.dearimgui.com/faq Q&A: Fonts, Text @@ -1223,7 +1224,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: ============== Q: How can I help? - A: - Businesses: please reach out to "omar AT dearimgui DOT com" if you work in a place using Dear ImGui! + A: - Businesses: please reach out to "omar AT discohello DOT com" if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding, we sustain and grow work on this project. >>> See https://github.com/ocornut/imgui/wiki/Funding From 01380c579715e62fb9a8d6ec0502c4ea83bfde6e Mon Sep 17 00:00:00 2001 From: ocornut Date: Sat, 25 Jul 2026 12:47:56 +0200 Subject: [PATCH 145/254] Version 1.92.9 --- docs/CHANGELOG.txt | 87 ++++++++++++++++++++++++---------------------- imgui.cpp | 2 +- imgui.h | 6 ++-- imgui_demo.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 2 +- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 2 +- 8 files changed, 54 insertions(+), 51 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0074103db..a3e036018 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -36,38 +36,32 @@ HOW TO UPDATE? - Please report any issue! ----------------------------------------------------------------------- - VERSION 1.92.9 WIP (In Progress) + VERSION 1.92.9 (Released 2026-07-25) ----------------------------------------------------------------------- +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.8 + Breaking Changes: -- TreeNode: commented out legacy name `ImGuiTreeNodeFlags_SpanTextWidth` which - was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. -- ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in - of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. -- Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` - which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: inputting a value with the keyboard doesn't write intermediate values to the backing variable. (#9476) Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. After: DragFloat() with user typing "123" --> write back 123 when validated/unfocused. You can enable the flag back for a given scope if needed by specific widget/behavior. + Read below for details. +- TreeNode: commented out legacy name `ImGuiTreeNodeFlags_SpanTextWidth` which + was obsoleted in 1.90.7 (May 2024). Use `ImGuiTreeNodeFlags_SpanLabelWidth`. +- ColorEdit: obsoleted SetColorEditOptions() function added in 1.51 (June 2017) in favor + of directly poking to io.ConfigColorEditFlags. More consistent and easier to discover. +- Drag and Drop: commented out legacy name `ImGuiDragDropFlags_SourceAutoExpirePayload` + which obsoleted in 1.90.9 (July 2024). Use `ImGuiDragDropFlags_PayloadAutoExpire`. - ImDrawData: marked `CmdListsCount` as obsolete (technically was obsoleted in 1.89.8). Before: `draw_data->CmdListsCount`, After: `draw_data->CmdLists.Size`. Other Changes: -- Windows: - - Clicking on a window's empty-space to move/focus a window checks - for lack of mouse button ownership. This gives an additional opportunity - for user code to bypass it without using a clickable item. (#9382) - - Clicking on a window's empty-space to move/focus a window checks - for lack of queued focus request. (#9382) - - Fixed double-click collapse toggle not owning the mouse button. If a - `SetNextWindowPos()` with pivot was queued in the same frame, the second - click could trigger another item in the same window. (#9439) [@Cleroth] -- Added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar` +- LiveEdit on/off: added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar` flags to configure the timing of applying edits of backing variables when typing values using a keyboard. (#9476, #701, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, @@ -106,13 +100,22 @@ Other Changes: - We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not expected to be a user preference but a programmer/widget preferences. - Also, we strive to make the toolkit consistent. + And we strive to make the toolkit consistent. +- Windows: + - Clicking on a window's empty-space to move/focus a window checks + for lack of mouse button ownership. This gives an additional opportunity + for user code to bypass it without using a clickable item. (#9382) + - Clicking on a window's empty-space to move/focus a window checks + for lack of queued focus request. (#9382) + - Fixed double-click collapse toggle not owning the mouse button. If a + `SetNextWindowPos()` with pivot was queued in the same frame, the second + click could trigger another item in the same window. (#9439) [@Cleroth] - Popups: - Added bool return value to `OpenPopup()`, `OpenPopupOnItemClick()` functions to notify when the popup was just opened. (#9429) - InputText: - Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409) - This is automatically scaled by `style.ScaleAllSizes()`. + It is automatically scaled by `style.ScaleAllSizes()`. - Reworked `io.ConfigInputTextEnterKeepActive` mode so that pressing Ctrl+Enter or Shift+Enter still allows to deactivate. (#9239) - Tables: @@ -135,9 +138,9 @@ Other Changes: even when no sort marker is displayed. Auto-fitting a column still accounts for the possible marker, so that sorting after an auto-fit doesn't clip the label. - Multi-Select: - - Reworked ImGuiMultiSelectFlags_NoAutoSelect as it carried side-effects that + - Reworked `ImGuiMultiSelectFlags_NoAutoSelect` as it carried side-effects that were hardcoded/designed to use multi-selection on checkboxes. (#9391) - Specifically removed those undocumented behaviors from _NoAutoSelect: + Specifically removed those undocumented behaviors from `_NoAutoSelect`: - Clicking a selected/checked item always unselect/uncheck. - Shift+Click inverts targets value and copy to range. - Shift+Keyboard copy selection source value to range. @@ -146,27 +149,27 @@ Other Changes: - Added `IMGUI_DISABLE_DEFAULT_FONT_BITMAP`/`IMGUI_DISABLE_DEFAULT_FONT_VECTOR` to disable embedding either fonts separately. (#9407) - Tweak `CalcTextSize()` awkward width rounding/ceiling code to reduce floating-point - imprecisions altering the result by 1 even at relatively small width. (#791) - - Better document the fact that ImFontAtlas::Clear()/ClearFonts() functions are + precision issues altering the result by 1 even at relatively small width. (#791) + - Better document the fact that `ImFontAtlas::Clear()`/`ClearFonts()` functions are unlikely to be useful nowadays. Better recover to an edge case of mistakenly - calling ClearFonts() during rendering. + calling `ClearFonts()` during rendering. - Fixed an issue where passing a manually created ImFontAtlas to CreateContext() would - incorrectly destroy it in DestroyContext() when ref-count gets back to zero. (#9426) - - Destroying an ImGui context using a ImFontAtlas checks that the later has no references. + incorrectly destroy it in `DestroyContext()` when ref-count gets back to zero. (#9426) + - Destroying an ImGui context using a `ImFontAtlas` checks that the later has no references. - Nav: - Fixed context menu activation with gamepad erroneously testing for _NavEnableKeyboard instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy] - TreeNode: - Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589) - Inputs: - - Added GetItemClickedCountWithSingleClickDelay() helper for easy disambiguation + - Added `GetItemClickedCountWithSingleClickDelay()` helper for easy disambiguation between single-click and double-click for actions that needs single-click to do something other than selection. (#8337) - Returns 1 on single-click but delayed by io.MouseSingleClickDelay. - - Returns 2 on double-click, and 2+ on subsequent repeated cicks. - - Added io.MouseSingleClickDelay to configure default delayed single click delay when - using GetItemClickedCountWithSingleClickDelay() or IsMouseReleasedWithDelay(). (#8337) - Note that io.MouseSingleClickDelay is always > io.MouseDoubleClickTime. + - Returns 2 on double-click, and 2+ on subsequent repeated clicks. + - Added `io.MouseSingleClickDelay` to configure default delayed single click delay when + using `GetItemClickedCountWithSingleClickDelay()` or `IsMouseReleasedWithDelay()`. (#8337) + Note that `io.MouseSingleClickDelay` is always `> io.MouseDoubleClickTime`. - ColorEdit: - Added `io.ConfigColorEditFlags` to read/modify current color edit/picker settings. - ColorPicker: added `ImGuiColorEditFlags_PickerNoRotate` to disable rotation of the @@ -182,13 +185,13 @@ Other Changes: - Settings: - Windows/Tables settings entries can now record the last used date in YYYYMMDD format, allowing tools to run to e.g. delete entries that haven't been used in X months. (#9460) - - Added bool io.ConfigIniSettingsSaveLastUsedDate to disable saving that info. (#9460) - - Added int io.ConfigIniSettingsAutoDiscardMonths to enable a mode where unused settings + - Added `bool io.ConfigIniSettingsSaveLastUsedDate` to disable saving that info. (#9460) + - Added `int io.ConfigIniSettingsAutoDiscardMonths` to enable a mode where unused settings are automatically discard after xx months. (#9460) - Added a trimming tool under Metrics->Settings, along with a yet-unexposed function. - - The current system date is fed through ImGuiPlatformIO::Platform_SessionDate, + - The current system date is fed through `ImGuiPlatformIO::Platform_SessionDate`, which is automatically set by a call to time() done during context creation. (#9460) - - Added IMGUI_DISABLE_TIME_FUNCTIONS to disable setting platform_io.Platform_SessionDate. + - Added `IMGUI_DISABLE_TIME_FUNCTIONS` to disable setting platform_io.Platform_SessionDate. A custom backend may still set it manually. (#9460) - DrawList: - Minor optimization to `AddLine()`, `AddLineH()`, `AddLineV()` functions. (#4091) @@ -197,24 +200,24 @@ Other Changes: - Demo: - Extract 'Widgets->Tree Nodes->Selectable Nodes' out of the 'Advanced' demo for clarity (manual reimplementation of basic selection). -- Misc: - - Added IM_DEBUG_BREAK() handler for GCC+AArch64/ARM64. [@tom-seddon] +- Debug Tools: + - Added `IM_DEBUG_BREAK()` handler for GCC+AArch64/ARM64. [@tom-seddon] - Backends: - Android: - - Clear mouse position on touch release (AMOTION_EVENT_ACTION_UP) to prevent + - Clear mouse position on touch release (`AMOTION_EVENT_ACTION_UP`) to prevent items from staying in hovered state. (#6627, #9474) [@Turtle-PB] - Metal4: - Added new Metal 4 backend (forked from Metal 3 backend). (#9458, #9451) [@AmelieHeinrich] - Added Metal-cpp support enabled with `IMGUI_IMPL_METAL_CPP` define. (#9461) [@MERL10N] - OpenGL2: - - Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture + - Backup and restore `GL_UNPACK_ROW_LENGTH` and `GL_UNPACK_ALIGNMENT` when updating texture to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] - OpenGL3: - GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. [#9427, #6577) [@perminovVS] - - Expose selected render state in ImGui_ImplOpenGL3_RenderState, allowing to - dynamically select between use of glBindSampler() and glTexParameter(). (#9378) - - Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT when updating texture + - Expose selected render state in `ImGui_ImplOpenGL3_RenderState`, allowing to + dynamically select between use of `glBindSampler()` and `glTexParameter()`. (#9378) + - Backup and restore `GL_UNPACK_ROW_LENGTH` and `GL_UNPACK_ALIGNMENT` when updating texture to avoid altering caller GL state. (#8802, #9473) [@Turtle-PB] - SDL2: - Restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard diff --git a/imgui.cpp b/imgui.cpp index 2eab3aae3..2c536fa8f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (main code and documentation) // Help: diff --git a/imgui.h b/imgui.h index 918d253d2..da664cb8e 100644 --- a/imgui.h +++ b/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (headers) // Help: @@ -29,8 +29,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.9 WIP" -#define IMGUI_VERSION_NUM 19287 +#define IMGUI_VERSION "1.92.9" +#define IMGUI_VERSION_NUM 19290 #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_demo.cpp b/imgui_demo.cpp index 27413b7aa..f9b3d0108 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (demo code) // Help: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6ec7478b9..acf377c55 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (drawing and font code) /* diff --git a/imgui_internal.h b/imgui_internal.h index 584914fc4..1a9369aa0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index efc03d7d9..43b5cd5a7 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (tables and columns code) /* diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index efd3a64ff..6c324e7b0 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 WIP +// dear imgui, v1.92.9 // (widgets code) /* From a9e7a8c880cf4a7fbf142b13a41bcbb1f43d0938 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 26 Jul 2026 15:42:48 +0200 Subject: [PATCH 146/254] ImDrawData: fixed a regression in 1.92.9 where legacy `ImDrawData::CmdListsCount` was always 0. --- docs/CHANGELOG.txt | 17 +++++++++++++++-- imgui.cpp | 4 +++- imgui_draw.cpp | 3 --- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a3e036018..35a67649a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,11 +35,24 @@ HOW TO UPDATE? and API updates have been a little more frequent lately. They are documented below and in imgui.cpp and should not affect all users. - Please report any issue! +----------------------------------------------------------------------- + VERSION 1.92.9b (In Progress) +----------------------------------------------------------------------- + +Breaking Changes: + +Other Changes: + +- ImDrawData: fixed a regression in 1.92.9 where legacy `ImDrawData::CmdListsCount` was always 0. + Prefer using `ImDrawData::CmdList.Size` anyway. + + + ----------------------------------------------------------------------- VERSION 1.92.9 (Released 2026-07-25) ----------------------------------------------------------------------- -Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.8 +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.9 Breaking Changes: @@ -1106,7 +1119,7 @@ Other Changes: not available. (#5924, #5562) - SDL3+DirectX11: Added SDL3+DirectX11 example. (#8956, #8957) [@tomaz82] - Win32+DirectX12: Rework synchronization logic. (#8961) [@RT2Code] - - Made examples's main.cpp consistent with returning 1 on error. + - Made examples' main.cpp consistent with returning 1 on error. ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index 2c536fa8f..da2780fd9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6243,7 +6243,9 @@ void ImGui::Render() ImDrawData* draw_data = &viewport->DrawDataP; for (ImDrawList* draw_list : draw_data->CmdLists) draw_list->_PopUnusedDrawCmd(); - +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + draw_data->CmdListsCount = draw_data->CmdLists.Size; +#endif g.IO.MetricsRenderVertices += draw_data->TotalVtxCount; g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index acf377c55..d89e36a9a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2340,9 +2340,6 @@ void ImDrawData::AddDrawList(ImDrawList* draw_list) { draw_list->_PopUnusedDrawCmd(); ImGui::AddDrawListToDrawDataEx(this, &CmdLists, draw_list); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - CmdListsCount = CmdLists.Size; -#endif } // For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! From ff135cf5e529e49519e8046381d9297454807c43 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 27 Jul 2026 21:49:46 +0200 Subject: [PATCH 147/254] Backends: DirectX11: removed obsolete comment that got left in this backend only. --- backends/imgui_impl_dx11.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index 6573d2742..73d7d6b70 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -574,7 +574,6 @@ bool ImGui_ImplDX11_CreateDeviceObjects() } // Create texture sampler - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) { D3D11_SAMPLER_DESC desc; ZeroMemory(&desc, sizeof(desc)); From 8c131021b0680fbfa22610737033d2fe6c54277f Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 29 Jul 2026 15:26:51 +0200 Subject: [PATCH 148/254] Update pull_request_template.md --- .github/pull_request_template.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d40b14ccf..27cb3e1dd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,5 +6,7 @@ 3. Consider running the [imgui_test_suite](https://github.com/ocornut/imgui_test_engine) or adding new tests to test for expected behaviors. -4. Clear this template before submitting your PR. +4. PLEASE DO NOT SUBMIT AI SLOP OR AUTOMATED REPORTS. Submissions that are lacking proper human review/judgement before being passed to us are not welcome. + +5. Clear this template before submitting your PR. From 950d27137e75663d7697cc21b7d566e67476ba28 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 29 Jul 2026 14:09:10 +0200 Subject: [PATCH 149/254] Tweaks. --- backends/imgui_impl_dx10.cpp | 10 +++++----- backends/imgui_impl_dx11.cpp | 10 +++++----- backends/imgui_impl_dx12.cpp | 8 ++++---- backends/imgui_impl_wgpu.cpp | 2 +- imgui.cpp | 18 +++++++++--------- imgui.h | 2 +- imgui_demo.cpp | 10 +++++----- imgui_tables.cpp | 11 ++++++----- 8 files changed, 36 insertions(+), 35 deletions(-) diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index 9858cbe94..9e570ef8c 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -148,7 +148,7 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* device->GSSetShader(nullptr); // Setup render state - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + const float blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; device->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); device->OMSetDepthStencilState(bd->pDepthStencilState, 0); device->RSSetState(bd->pRasterizerState); @@ -446,7 +446,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() PS_INPUT main(VS_INPUT input)\ {\ PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.0f, 1.0f));\ output.col = input.col;\ output.uv = input.uv;\ return output;\ @@ -566,10 +566,10 @@ bool ImGui_ImplDX10_CreateDeviceObjects() desc.AddressU = D3D10_TEXTURE_ADDRESS_CLAMP; desc.AddressV = D3D10_TEXTURE_ADDRESS_CLAMP; desc.AddressW = D3D10_TEXTURE_ADDRESS_CLAMP; - desc.MipLODBias = 0.f; + desc.MipLODBias = 0.0f; desc.ComparisonFunc = D3D10_COMPARISON_ALWAYS; - desc.MinLOD = 0.f; - desc.MaxLOD = 0.f; + desc.MinLOD = 0.0f; + desc.MaxLOD = 0.0f; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear); desc.Filter = D3D10_FILTER_MIN_MAG_MIP_POINT; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest); diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index 73d7d6b70..73f722a08 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -155,7 +155,7 @@ static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11D device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used.. // Setup render state - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + const float blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff); device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0); device_ctx->RSSetState(bd->pRasterizerState); @@ -462,7 +462,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects() PS_INPUT main(VS_INPUT input)\ {\ PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.0f, 1.0f));\ output.col = input.col;\ output.uv = input.uv;\ return output;\ @@ -581,10 +581,10 @@ bool ImGui_ImplDX11_CreateDeviceObjects() desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; - desc.MipLODBias = 0.f; + desc.MipLODBias = 0.0f; desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; - desc.MinLOD = 0.f; - desc.MaxLOD = 0.f; + desc.MinLOD = 0.0f; + desc.MaxLOD = 0.0f; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerLinear); desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSamplerNearest); diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 157384abf..53c7b3dd0 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -207,7 +207,7 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic command_list->SetGraphicsRoot32BitConstants(0, 16, &vertex_constant_buffer, 0); // Setup blend factor - const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f }; + const float blend_factor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; command_list->OMSetBlendFactor(blend_factor); } @@ -614,11 +614,11 @@ bool ImGui_ImplDX12_CreateDeviceObjects() staticSampler[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; staticSampler[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; staticSampler[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - staticSampler[0].MipLODBias = 0.f; + staticSampler[0].MipLODBias = 0.0f; staticSampler[0].MaxAnisotropy = 0; staticSampler[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; staticSampler[0].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; - staticSampler[0].MinLOD = 0.f; + staticSampler[0].MinLOD = 0.0f; staticSampler[0].MaxLOD = D3D12_FLOAT32_MAX; staticSampler[0].ShaderRegister = 0; staticSampler[0].RegisterSpace = 0; @@ -721,7 +721,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() PS_INPUT main(VS_INPUT input)\ {\ PS_INPUT output;\ - output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\ + output.pos = mul( ProjectionMatrix, float4(input.pos.xy, 0.0f, 1.0f));\ output.col = input.col;\ output.uv = input.uv;\ return output;\ diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index cd952cdc2..9f0124f0c 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -449,7 +449,7 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas wgpuRenderPassEncoderSetBindGroup(ctx, 0, bd->renderResources.CommonBindGroupLinear, 0, nullptr); // Setup blend factor - WGPUColor blend_color = { 0.f, 0.f, 0.f, 0.f }; + WGPUColor blend_color = { 0.0f, 0.0f, 0.0f, 0.0f }; wgpuRenderPassEncoderSetBlendConstant(ctx, &blend_color); } diff --git a/imgui.cpp b/imgui.cpp index da2780fd9..b480b50ad 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2860,7 +2860,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b) { - float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; + float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.0f; int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); @@ -2891,20 +2891,20 @@ ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) { - float K = 0.f; + float K = 0.0f; if (g < b) { ImSwap(g, b); - K = -1.f; + K = -1.0f; } if (r < g) { ImSwap(r, g); - K = -2.f / 6.f - K; + K = -2.0f / 6.0f - K; } const float chroma = r - (g < b ? g : b); - out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f)); + out_h = ImFabs(K + (g - b) / (6.0f * chroma + 1e-20f)); out_s = chroma / (r + 1e-20f); out_v = r; } @@ -7746,10 +7746,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (flags & ImGuiWindowFlags_AlwaysAutoResize) { if (!window_size_x_set_by_api) - window->Size.x = window->SizeFull.x = 0.f; + window->Size.x = window->SizeFull.x = 0.0f; if (!window_size_y_set_by_api) - window->Size.y = window->SizeFull.y = 0.f; - window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f); + window->Size.y = window->SizeFull.y = 0.0f; + window->ContentSize = window->ContentSizeIdeal = ImVec2(0.0f, 0.0f); } } @@ -18519,7 +18519,7 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) Checkbox("Ctrl+C: copy path", &tool->OptCopyToClipboardOnCtrlC); PopStyleVar(); SameLine(); - TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*"); + TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.0f, 1.0f, 0.3f, 1.0f) : ImVec4(), "*COPIED*"); if (tool->OptCopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused)) { tool->CopyToClipboardLastTime = (float)g.Time; diff --git a/imgui.h b/imgui.h index da664cb8e..c7f5c92ac 100644 --- a/imgui.h +++ b/imgui.h @@ -3298,7 +3298,7 @@ enum ImDrawFlags_ // Stroke options ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. - //ImDrawFlags_Closed = 1 << 0, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. + //ImDrawFlags_Closed = 1, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f9b3d0108..67aa1721a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1747,7 +1747,7 @@ static void DemoWindowWidgetsDragAndDrop() if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) { - int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); + int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.0f ? -1 : 1); if (n_next >= 0 && n_next < IM_COUNTOF(item_names)) { item_names[n] = item_names[n_next]; @@ -2201,7 +2201,7 @@ static void DemoWindowWidgetsProgressBars() char buf[32]; sprintf(buf, "%d/%d", (int)(progress * 1753), 1753); - ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); + ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f), buf); // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value. // Adjust the factor if you want to adjust the animation speed. @@ -9094,7 +9094,7 @@ struct ExampleAppConsole static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; } static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; } static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } - static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } + static void Strtrimblanks(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } void ClearLog() { @@ -9243,10 +9243,10 @@ struct ExampleAppConsole if (ImGui::InputText("Input", InputBuf, IM_COUNTOF(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) { char* s = InputBuf; - Strtrim(s); + Strtrimblanks(s); if (s[0]) ExecCommand(s); - strcpy(s, ""); + s[0] = 0; reclaim_focus = true; } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 43b5cd5a7..4799d5648 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -490,17 +490,18 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // FIXME: Because inner_window's Scrollbar doesn't know about border size, since it's not encoded in window->WindowBorderSize, // it already overlaps it and doesn't need an extra offset. Ideally we should be able to pass custom border size with // different x/y values to BeginChild(). + const float border_size = TABLE_BORDER_SIZE; if (flags & ImGuiTableFlags_BordersOuterV) { - table->HostClipRect.Min.x = ImMin(table->HostClipRect.Min.x + TABLE_BORDER_SIZE, table->HostClipRect.Max.x); + table->HostClipRect.Min.x = ImMin(table->HostClipRect.Min.x + border_size, table->HostClipRect.Max.x); if (inner_window->DecoOuterSizeX2 == 0.0f) - table->HostClipRect.Max.x = ImMax(table->HostClipRect.Max.x - TABLE_BORDER_SIZE, table->HostClipRect.Min.x); + table->HostClipRect.Max.x = ImMax(table->HostClipRect.Max.x - border_size, table->HostClipRect.Min.x); } if (flags & ImGuiTableFlags_BordersOuterH) { - table->HostClipRect.Min.y = ImMin(table->HostClipRect.Min.y + TABLE_BORDER_SIZE, table->HostClipRect.Max.y); + table->HostClipRect.Min.y = ImMin(table->HostClipRect.Min.y + border_size, table->HostClipRect.Max.y); if (inner_window->DecoOuterSizeY2 == 0.0f) - table->HostClipRect.Max.y = ImMax(table->HostClipRect.Max.y - TABLE_BORDER_SIZE, table->HostClipRect.Min.y); + table->HostClipRect.Max.y = ImMax(table->HostClipRect.Max.y - border_size, table->HostClipRect.Min.y); } } @@ -3626,7 +3627,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label if (pass == 1) { // Draw border - draw_list->AddLine(bg_shape[0], bg_shape[3], TableGetColumnBorderCol(table, order_n, column_n)); + draw_list->AddLine(bg_shape[0], bg_shape[3], TableGetColumnBorderCol(table, order_n, column_n), TABLE_BORDER_SIZE); } } PopClipRect(); From e47e6b0d9de3433dd9c3fee17e6e509163256fc9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 16:03:42 +0200 Subject: [PATCH 150/254] Fonts: made GetDefaultCompressedFontDataProggyClean()/GetDefaultCompressedFontDataProggyForever() undocumented non-static. (#9493) --- imgui_draw.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d89e36a9a..37ab7b4fc 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3159,10 +3159,10 @@ static void Decode85(const unsigned char* src, unsigned char* dst) } } #if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_BITMAP) -static const char* GetDefaultCompressedFontDataProggyClean(int* out_size); +const char* ImGui_GetDefaultCompressedFontDataProggyClean(int* out_size); #endif #if !defined(IMGUI_DISABLE_DEFAULT_FONT) && !defined(IMGUI_DISABLE_DEFAULT_FONT_VECTOR) -static const char* GetDefaultCompressedFontDataProggyForever(int* out_size); +const char* ImGui_GetDefaultCompressedFontDataProggyForever(int* out_size); #endif // This duplicates some of the logic in UpdateFontsNewFrame() which is a bit chicken-and-eggy/tricky to extract due to variety of codepaths and possible initialization ordering. @@ -3201,7 +3201,7 @@ ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template) font_cfg.GlyphOffset.y += 1.0f * (font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units int ttf_compressed_size = 0; - const char* ttf_compressed = GetDefaultCompressedFontDataProggyClean(&ttf_compressed_size); + const char* ttf_compressed = ImGui_GetDefaultCompressedFontDataProggyClean(&ttf_compressed_size); return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg); #else IM_ASSERT(0 && "Function is disabled in this build."); @@ -3229,7 +3229,7 @@ ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template) font_cfg.GlyphOffset.y += 0.5f * (font_cfg.SizePixels / 16.0f); // Closer match ProggyClean + avoid descenders going too high (with current code). int ttf_compressed_size = 0; - const char* ttf_compressed = GetDefaultCompressedFontDataProggyForever(&ttf_compressed_size); + const char* ttf_compressed = ImGui_GetDefaultCompressedFontDataProggyForever(&ttf_compressed_size); return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg); #else IM_ASSERT(0 && "Function is disabled in this build."); @@ -6536,7 +6536,7 @@ static const unsigned char proggy_clean_ttf_compressed_data[9583] = 239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247, }; -static const char* GetDefaultCompressedFontDataProggyClean(int* out_size) +const char* ImGui_GetDefaultCompressedFontDataProggyClean(int* out_size) { *out_size = proggy_clean_ttf_compressed_size; return (const char*)proggy_clean_ttf_compressed_data; @@ -6801,7 +6801,7 @@ static const unsigned char proggy_forever_minimal_ttf_compressed_data[14562] = 3,36,0,229,13,183,147,132,7,42,175,187,66,0,0,0,0,229,178,59,232,5,250,48,120,202,241, }; -static const char* GetDefaultCompressedFontDataProggyForever(int* out_size) +const char* ImGui_GetDefaultCompressedFontDataProggyForever(int* out_size) { *out_size = proggy_forever_minimal_ttf_compressed_size; return (const char*)proggy_forever_minimal_ttf_compressed_data; From ffbf21af6b61157473a711738ad6da0641bb267d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 16:12:31 +0200 Subject: [PATCH 151/254] Demo: Custom Rendering: enable LiveEdit. Tweaks. --- imgui_demo.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 67aa1721a..85e1bc4b4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10265,6 +10265,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) { IMGUI_DEMO_MARKER("Examples/Custom rendering/Primitives"); ImGui::PushItemWidth(-ImGui::GetFontSize() * 15); + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Draw gradients @@ -10291,7 +10292,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Draw a bunch of primitives ImGui::Text("All primitives"); - static float sz = 36.0f; + static float sz = 42.0f; static float thickness = 3.0f; static int ngon_sides = 6; static bool circle_segments_override = false; @@ -10385,6 +10386,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) x += sz + spacing; ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); + ImGui::PopItemFlag(); ImGui::PopItemWidth(); ImGui::EndTabItem(); } From ed252c55aadd46e4ef78d85f795d6e3c4191a911 Mon Sep 17 00:00:00 2001 From: thedmd Date: Thu, 30 Jul 2026 21:40:05 +0200 Subject: [PATCH 152/254] Tables: fixed Y position of lower horizontal border being off by one pixel when vertical ones are not also enabled. Attributed to thedmd who reported this to me. --- docs/CHANGELOG.txt | 2 ++ imgui_tables.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 35a67649a..fc3d69650 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,8 @@ Breaking Changes: Other Changes: +- Tables: fixed Y2 position of lower horizontal border (ImGuiTableFlags_BordersOuterH) + being off by one pixel when vertical ones are not also enabled. [@thedmd] - ImDrawData: fixed a regression in 1.92.9 where legacy `ImDrawData::CmdListsCount` was always 0. Prefer using `ImDrawData::CmdList.Size` anyway. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 4799d5648..a6185d85d 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3026,7 +3026,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if (table->Flags & ImGuiTableFlags_BordersOuterH) { inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size); - inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y - border_size, outer_col, border_size); } } if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) From b9adace7b575b401cef8cd77bfee1fed4706b913 Mon Sep 17 00:00:00 2001 From: thedmd Date: Thu, 30 Jul 2026 22:02:50 +0200 Subject: [PATCH 153/254] Tables: fixed X position of angled borders being off by half a pixel. --- docs/CHANGELOG.txt | 6 ++++-- imgui_tables.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fc3d69650..642dbded5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,8 +43,10 @@ Breaking Changes: Other Changes: -- Tables: fixed Y2 position of lower horizontal border (ImGuiTableFlags_BordersOuterH) - being off by one pixel when vertical ones are not also enabled. [@thedmd] +- Tables: + - Fixed Y2 position of lower horizontal border (ImGuiTableFlags_BordersOuterH) + being off by one pixel when vertical ones are not also enabled. [@thedmd] + - Fixed X position of angled borders being off by half a pixel. [@thedmd] - ImDrawData: fixed a regression in 1.92.9 where legacy `ImDrawData::CmdListsCount` was always 0. Prefer using `ImDrawData::CmdList.Size` anyway. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a6185d85d..3e7065bf9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3627,7 +3627,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label if (pass == 1) { // Draw border - draw_list->AddLine(bg_shape[0], bg_shape[3], TableGetColumnBorderCol(table, order_n, column_n), TABLE_BORDER_SIZE); + draw_list->AddLine(bg_shape[0] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), bg_shape[3] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), TABLE_BORDER_SIZE); } } PopClipRect(); From 448ef2baa994f24a15cbd6cadeea3f64323e2e4e Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 13:09:40 +0200 Subject: [PATCH 154/254] DragXXX, SliderXXX: fixed a regression in 1.92.9 where clicking a previously Ctrl+Clicked field would flick it to text edit mode for a frame. (#9476, #701) --- docs/CHANGELOG.txt | 2 ++ imgui_internal.h | 1 + imgui_widgets.cpp | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 642dbded5..2265df94d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,8 @@ Breaking Changes: Other Changes: +- DragXXX, SliderXXX: fixed a regression in 1.92.9 where clicking a previously + Ctrl+Clicked field would flick it to text edit mode for a frame. (#9476, #701) - Tables: - Fixed Y2 position of lower horizontal border (ImGuiTableFlags_BordersOuterH) being off by one pixel when vertical ones are not also enabled. [@thedmd] diff --git a/imgui_internal.h b/imgui_internal.h index 1a9369aa0..c7efaf44e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1265,6 +1265,7 @@ struct IMGUI_API ImGuiInputTextState ImStbTexteditState* Stb; // State for stb_textedit.h ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. ImGuiID ID; // widget id owning the text state + //int LastFrameActive; // Last used frame-count (unused) int TextLen; // UTF-8 length of the string in TextA (in bytes) const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). For _ReadOnly fields, pointer will be null outside the InputText() call. ImVector TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1). diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6c324e7b0..3c46db524 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4606,6 +4606,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) if (!state->EditedBefore) return; //IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id); + state->EditedBefore = false; g.InputTextDeactivatedState.ID = state->ID; g.InputTextDeactivatedState.ElapseFrame = g.FrameCount + 1; if (state->Flags & ImGuiInputTextFlags_ReadOnly) @@ -4997,6 +4998,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (state != NULL && state->ID == id) { state->Flags = flags; + //state->LastFrameActive = g.FrameCount; // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset) if (is_wordwrap && state->WrapWidth != wrap_width) @@ -5484,9 +5486,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Otherwise request text input ahead for next frame. if (g.ActiveId == id && clear_active_id) { - state->ID = 0; // To avoid InputTextDeactivateHook() unnecessarily running, which wouldn't be harmful but wasteful. + state->EditedBefore = false; // Data already applied: avoid InputTextDeactivateHook() taking a record now or later if same id is activated again without editing. ClearActiveID(); - state->ID = id; } // Render frame From f1cc2ae15e53a861a874c3034aae6798fde194ab Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 15:51:26 +0200 Subject: [PATCH 155/254] Version 1.92.9b --- backends/imgui_impl_android.cpp | 2 +- backends/imgui_impl_android.h | 2 +- docs/CHANGELOG.txt | 7 +++---- imgui.cpp | 2 +- imgui.h | 6 +++--- imgui_demo.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 2 +- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 2 +- 10 files changed, 14 insertions(+), 15 deletions(-) diff --git a/backends/imgui_impl_android.cpp b/backends/imgui_impl_android.cpp index 08b3de08a..21a7ae526 100644 --- a/backends/imgui_impl_android.cpp +++ b/backends/imgui_impl_android.cpp @@ -1,5 +1,5 @@ // dear imgui: Platform Binding for Android native app -// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3) +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) // Implemented features: // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values are obsolete since 1.87 and not supported since 1.91.5] diff --git a/backends/imgui_impl_android.h b/backends/imgui_impl_android.h index f6e41039a..8f22e9b76 100644 --- a/backends/imgui_impl_android.h +++ b/backends/imgui_impl_android.h @@ -1,5 +1,5 @@ // dear imgui: Platform Binding for Android native app -// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3) +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) // Implemented features: // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values are obsolete since 1.87 and not supported since 1.91.5] diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2265df94d..fbfd2aa55 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -36,12 +36,12 @@ HOW TO UPDATE? - Please report any issue! ----------------------------------------------------------------------- - VERSION 1.92.9b (In Progress) + VERSION 1.92.9b (Released 2026-07-31) ----------------------------------------------------------------------- -Breaking Changes: +Decorated log and release notes: https://github.com/ocornut/imgui/releases/tag/v1.92.9b -Other Changes: +Changes: - DragXXX, SliderXXX: fixed a regression in 1.92.9 where clicking a previously Ctrl+Clicked field would flick it to text edit mode for a frame. (#9476, #701) @@ -53,7 +53,6 @@ Other Changes: Prefer using `ImDrawData::CmdList.Size` anyway. - ----------------------------------------------------------------------- VERSION 1.92.9 (Released 2026-07-25) ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index b480b50ad..7fa2d49e8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (main code and documentation) // Help: diff --git a/imgui.h b/imgui.h index c7f5c92ac..611c5f28d 100644 --- a/imgui.h +++ b/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (headers) // Help: @@ -29,8 +29,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.9" -#define IMGUI_VERSION_NUM 19290 +#define IMGUI_VERSION "1.92.9b" +#define IMGUI_VERSION_NUM 19291 #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_demo.cpp b/imgui_demo.cpp index 85e1bc4b4..9f841b3c4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (demo code) // Help: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 37ab7b4fc..e582f5cd8 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (drawing and font code) /* diff --git a/imgui_internal.h b/imgui_internal.h index c7efaf44e..d2a8595a1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3e7065bf9..f8fab3778 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (tables and columns code) /* diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3c46db524..5ed7d6feb 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9 +// dear imgui, v1.92.9b // (widgets code) /* From 9b7699f32597e7c7a799f22b1860ac586c2857b9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 22:07:01 +0200 Subject: [PATCH 156/254] Backends: amend comments to state that bilinear filtering is required ahead of 1.93. --- backends/imgui_impl_allegro5.cpp | 3 +-- backends/imgui_impl_dx10.cpp | 3 +-- backends/imgui_impl_dx12.cpp | 2 +- backends/imgui_impl_metal.mm | 2 +- backends/imgui_impl_opengl2.cpp | 3 +-- backends/imgui_impl_opengl3.cpp | 3 +-- backends/imgui_impl_sdlgpu3.cpp | 2 +- backends/imgui_impl_sdlrenderer2.cpp | 3 +-- backends/imgui_impl_sdlrenderer3.cpp | 3 +-- backends/imgui_impl_vulkan.cpp | 3 +-- backends/imgui_impl_wgpu.cpp | 3 +-- docs/BACKENDS.md | 4 +--- 12 files changed, 12 insertions(+), 22 deletions(-) diff --git a/backends/imgui_impl_allegro5.cpp b/backends/imgui_impl_allegro5.cpp index 79b169da8..bd877f322 100644 --- a/backends/imgui_impl_allegro5.cpp +++ b/backends/imgui_impl_allegro5.cpp @@ -268,8 +268,7 @@ void ImGui_ImplAllegro5_UpdateTexture(ImTextureData* tex) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture (bilinear sampling is required) const int new_bitmap_flags = al_get_new_bitmap_flags(); int new_bitmap_format = al_get_new_bitmap_format(); al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index 9e570ef8c..35378612d 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -557,8 +557,7 @@ bool ImGui_ImplDX10_CreateDeviceObjects() bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState); } - // Create texture sampler - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture sampler (bilinear sampling is required) { D3D10_SAMPLER_DESC desc; ZeroMemory(&desc, sizeof(desc)); diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 53c7b3dd0..034882856 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -608,7 +608,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects() param[1].DescriptorTable.pDescriptorRanges = &descRange; param[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. + // Bilinear sampling is required D3D12_STATIC_SAMPLER_DESC staticSampler[1] = {}; staticSampler[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; staticSampler[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 445c4b36c..1c0660628 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -584,7 +584,7 @@ void ImGui_ImplMetal_Shutdown() return [[MetalBuffer alloc] initWithBuffer:backing]; } -// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. +// Bilinear sampling is required. - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device { NSError* error = nil; diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 111f37413..e6d195533 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -276,8 +276,7 @@ void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex) const void* pixels = tex->GetPixels(); GLuint gl_texture_id = 0; - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Upload texture to graphics system (bilinear sampling is required). GLint last_texture; GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); GL_CALL(glGenTextures(1, &gl_texture_id)); diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index ff9980035..7be882e06 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -689,8 +689,7 @@ void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex) const void* pixels = tex->GetPixels(); GLuint gl_texture_id = 0; - // Upload texture to graphics system - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Upload texture to graphics system (bilinear sampling is required). GLint last_texture; GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); GL_CALL(glGenTextures(1, &gl_texture_id)); diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 8f0408c82..9328b0a32 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -591,7 +591,7 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects() if (bd->TexSamplerLinear == nullptr) { - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. + // Bilinear sampling is required. SDL_GPUSamplerCreateInfo sampler_info = {}; sampler_info.min_filter = SDL_GPU_FILTER_LINEAR; sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR; diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index d9f329bd1..e295e8677 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -213,8 +213,7 @@ void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture (bilinear sampling is required) SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index d4c043cbc..cb975393e 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -242,8 +242,7 @@ void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); - // Create texture - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create texture (bilinear sampling is required) SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height); IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!"); SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch()); diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index ef7dd625f..57ebdb064 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -1153,8 +1153,7 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() check_vk_result(err); } - // Create samplers - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. + // Create samplers (bilinear sampling is required) if (!bd->SamplerLinear || !bd->SamplerNearest) { VkSamplerCreateInfo info = {}; diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 9f0124f0c..ce5974b41 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -844,8 +844,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() ImGui_ImplWGPU_CreateUniformBuffer(); - // Create samplers (Linear/Nearest) - // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) + // Create samplers (Linear/Nearest) (bilinear sampling is required) WGPUSamplerDescriptor sampler_desc = {}; sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index 2f3a802d8..f72391f3e 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -234,9 +234,7 @@ void MyImGuiBackend_RenderDrawData(ImDrawData* draw_data) // - Scissor enabled MyEngineSetupenderState(); - // TODO: Setup texture sampling state - // - Sample with bilinear filtering (NOT point/nearest filtering). - // - Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. + // TODO: Setup texture sampler to bilinear (NOT point/nearest filtering). // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize From ebd752a0ece30054ba2e165ee06a4ef1a1500a97 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Aug 2026 14:42:56 +0200 Subject: [PATCH 157/254] Version 1.93.0 WIP --- docs/CHANGELOG.txt | 9 +++++++++ imgui.cpp | 2 +- imgui.h | 6 +++--- imgui_demo.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 2 +- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 2 +- 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fbfd2aa55..d9b60016d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,6 +35,15 @@ HOW TO UPDATE? and API updates have been a little more frequent lately. They are documented below and in imgui.cpp and should not affect all users. - Please report any issue! +----------------------------------------------------------------------- + VERSION 1.93.0 WIP (In Progress) +----------------------------------------------------------------------- + +Breaking Changes: + +Other Changes: + + ----------------------------------------------------------------------- VERSION 1.92.9b (Released 2026-07-31) ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index 7fa2d49e8..fbad03746 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (main code and documentation) // Help: diff --git a/imgui.h b/imgui.h index 611c5f28d..7830580a7 100644 --- a/imgui.h +++ b/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (headers) // Help: @@ -29,8 +29,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.92.9b" -#define IMGUI_VERSION_NUM 19291 +#define IMGUI_VERSION "1.93.0 WIP" +#define IMGUI_VERSION_NUM 19292 #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_demo.cpp b/imgui_demo.cpp index 9f841b3c4..e013f510c 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (demo code) // Help: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e582f5cd8..fa8ea9cc0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (drawing and font code) /* diff --git a/imgui_internal.h b/imgui_internal.h index d2a8595a1..208c04cb4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f8fab3778..19f7933ed 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (tables and columns code) /* diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 5ed7d6feb..5bd6da9fa 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.92.9b +// dear imgui, v1.93.0 WIP // (widgets code) /* From b647af21008696a46c2aad2f444d6bd9b1798a2f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Aug 2026 15:00:05 +0200 Subject: [PATCH 158/254] Fonts: AddFontDefault() uses framebuffer scale. If running before DisplaySize is set, assume a different default on Apple. --- docs/CHANGELOG.txt | 5 +++++ imgui_draw.cpp | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d9b60016d..e854fbc69 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -43,6 +43,11 @@ Breaking Changes: Other Changes: +- Fonts: + - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic + to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of + ProggyClean on most Mac setups by default. + ----------------------------------------------------------------------- VERSION 1.92.9b (Released 2026-07-31) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index fa8ea9cc0..814a09352 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3168,11 +3168,15 @@ const char* ImGui_GetDefaultCompressedFontDataProggyForever(int* out_size); // This duplicates some of the logic in UpdateFontsNewFrame() which is a bit chicken-and-eggy/tricky to extract due to variety of codepaths and possible initialization ordering. static float GetExpectedContextFontSize(ImGuiContext* ctx) { - return ((ctx->Style.FontSizeBase > 0.0f) ? ctx->Style.FontSizeBase : 13.0f) * ctx->Style.FontScaleMain * ctx->Style.FontScaleDpi; + float fb_scale = (ctx->IO.DisplayFramebufferScale.x != 0.0f) ? ctx->IO.DisplayFramebufferScale.x : 1.0f; + if (ctx->IO.DisplayFramebufferScale.x == 1.0f && ctx->IO.DisplaySize.x <= 0.0f && ctx->IO.ConfigMacOSXBehaviors) + fb_scale = 2.0f; // If called before backend had a chance to init DisplayFramebufferScale. default to expecting Retina. + float scale = ctx->Style.FontScaleMain * ctx->Style.FontScaleDpi * fb_scale; + return ((ctx->Style.FontSizeBase > 0.0f) ? ctx->Style.FontSizeBase : 13.0f) * scale; } // Legacy function with heuristic to select Pixel or Vector font. -// The selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold at the time of adding the default font. +// The selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi * io.DisplayFramebufferScale) reaching a small threshold at the time of adding the default font. // Prefer calling AddFontDefaultVector() or AddFontDefaultBitmap() based on your own logic. ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg) { From fdc11522811cac1e246ed59c11208af29ee08063 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Aug 2026 16:45:56 +0200 Subject: [PATCH 159/254] (Breaking) Style: obsoleted style.CurveTessellationTol in favor of style.CurveTesselationMaxError. cc #3127, #3664, #3665 --- docs/CHANGELOG.txt | 5 +++++ imgui.cpp | 35 ++++++++++++++++++++++++----------- imgui.h | 5 ++++- imgui_demo.cpp | 5 +++-- imgui_draw.cpp | 26 ++++++++++++++------------ imgui_internal.h | 6 +++--- 6 files changed, 53 insertions(+), 29 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e854fbc69..69aa3477c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,11 @@ HOW TO UPDATE? Breaking Changes: +- Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in + favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. It is + easier to tweak + this allows us to easily scale error threshold on high density screens. + - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). + Other Changes: - Fonts: diff --git a/imgui.cpp b/imgui.cpp index fbad03746..b1791da0e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,6 +395,8 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. + - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). - 2026/07/20 (1.92.9) - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: inputting a value with the keyboard doesn't write intermediate values to backing variable. (#9476) - Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. @@ -1570,10 +1572,12 @@ ImGuiStyle::ImGuiStyle() DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + + // Rendering & Tesselation AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). - CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + CurveTessellationMaxError = 1.12f; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Behaviors @@ -1586,6 +1590,9 @@ ImGuiStyle::ImGuiStyle() // [Internal] _MainScale = 1.0f; _NextFrameFontSizeBase = 0.0f; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + CurveTessellationTol = 0.0f; // Old CurveTessellationTol = CurveTessellationMaxError*CurveTessellationMaxError. +#endif // Default theme ImGui::StyleColorsDark(this); @@ -2100,7 +2107,7 @@ ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec } // Closely mimics PathBezierToCasteljau() in imgui_draw.cpp -static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float max_error_sqr, int level) { float dx = x4 - x1; float dy = y4 - y1; @@ -2108,7 +2115,7 @@ static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_cl float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; - if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + if ((d2 + d3) * (d2 + d3) < max_error_sqr * (dx * dx + dy * dy)) { ImVec2 p_current(x4, y4); ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); @@ -2128,20 +2135,21 @@ static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_cl float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f; float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f; float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f; - ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); - ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, max_error_sqr, level + 1); + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, max_error_sqr, level + 1); } } -// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol +// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationMaxError // Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically. -ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol) +ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float max_error) { - IM_ASSERT(tess_tol > 0.0f); + IM_ASSERT(max_error > 0.0f); ImVec2 p_last = p1; ImVec2 p_closest; float p_closest_dist2 = FLT_MAX; - ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); + float max_error_sqr = max_error * max_error; + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, max_error_sqr, 0); return p_closest; } @@ -5572,7 +5580,7 @@ static void SetupDrawListSharedData() for (ImGuiViewportP* viewport : g.Viewports) virtual_space.Add(viewport->GetMainRect()); g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); - g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + g.DrawListSharedData.CurveTessellationMaxError = g.Style.CurveTessellationMaxError; g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; if (g.Style.AntiAliasedLines) @@ -11098,7 +11106,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); + IM_ASSERT(g.Style.CurveTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting!"); @@ -11138,6 +11146,11 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) { return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData); }; if (g.IO.SetClipboardTextFn != NULL && (g.PlatformIO.Platform_SetClipboardTextFn == NULL || g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl)) g.PlatformIO.Platform_SetClipboardTextFn = [](ImGuiContext* ctx, const char* text) { return ctx->IO.SetClipboardTextFn(ctx->IO.ClipboardUserData, text); }; + + // Remap legacy CurveTessellationTol into CurveTessellationMaxError (OBSOLETED in 1.93.0, August 2026) + if (g.Style.CurveTessellationTol != 0.0f) + g.Style.CurveTessellationMaxError = ImSqrt(g.Style.CurveTessellationTol); + g.Style.CurveTessellationTol = 0.0f; #endif } diff --git a/imgui.h b/imgui.h index 7830580a7..4bf5fe38b 100644 --- a/imgui.h +++ b/imgui.h @@ -2373,10 +2373,12 @@ struct ImGuiStyle ImVec2 DisplayWindowPadding; // Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen. ImVec2 DisplaySafeAreaPadding; // Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured). float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. + + // Rendering & Tesselation bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). - float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float CurveTessellationMaxError; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Colors @@ -2400,6 +2402,7 @@ struct ImGuiStyle // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float CurveTessellationTol; // [OBSOLETE] Old CurveTessellationTol = New CurveTessellationMaxError*CurveTessellationMaxError. // Changed in 1.93.0 // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9. #endif }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e013f510c..314488d94 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8828,8 +8828,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) Checkbox("Anti-aliased fill", &style.AntiAliasedFill); PushItemWidth(GetFontSize() * 8); - DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); - if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; + DragFloat("Curve Tessellation Max Error", &style.CurveTessellationMaxError, 0.02f, 0.10f, 10.0f, "%.2f"); + if (style.CurveTessellationMaxError < 0.10f) + style.CurveTessellationMaxError = 0.10f; // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 814a09352..a84fc3ef1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1370,7 +1370,7 @@ ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p } // Closely mimics ImBezierCubicClosestPointCasteljau() in imgui.cpp -static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) +static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float max_error_sqr, int level) { float dx = x4 - x1; float dy = y4 - y1; @@ -1378,7 +1378,7 @@ static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, fl float d3 = (x3 - x4) * dy - (y3 - y4) * dx; d2 = (d2 >= 0) ? d2 : -d2; d3 = (d3 >= 0) ? d3 : -d3; - if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) + if ((d2 + d3) * (d2 + d3) < max_error_sqr * (dx * dx + dy * dy)) { path->push_back(ImVec2(x4, y4)); } @@ -1390,16 +1390,16 @@ static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, fl float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f; float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f; - PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); - PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); + PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, max_error_sqr, level + 1); + PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, max_error_sqr, level + 1); } } -static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float tess_tol, int level) +static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float max_error_sqr, int level) { float dx = x3 - x1, dy = y3 - y1; float det = (x2 - x3) * dy - (y2 - y3) * dx; - if (det * det * 4.0f < tess_tol * (dx * dx + dy * dy)) + if (det * det * 4.0f < max_error_sqr * (dx * dx + dy * dy)) { path->push_back(ImVec2(x3, y3)); } @@ -1408,8 +1408,8 @@ static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1 float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; - PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, tess_tol, level + 1); - PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, tess_tol, level + 1); + PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, max_error_sqr, level + 1); + PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, max_error_sqr, level + 1); } } @@ -1418,8 +1418,9 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons ImVec2 p1 = _Path.back(); if (num_segments == 0) { - IM_ASSERT(_Data->CurveTessellationTol > 0.0f); - PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated + IM_ASSERT(_Data->CurveTessellationMaxError > 0.0f); + float max_error_sqr = _Data->CurveTessellationMaxError * _Data->CurveTessellationMaxError; + PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, max_error_sqr, 0); // Auto-tessellated } else { @@ -1434,8 +1435,9 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, ImVec2 p1 = _Path.back(); if (num_segments == 0) { - IM_ASSERT(_Data->CurveTessellationTol > 0.0f); - PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated + IM_ASSERT(_Data->CurveTessellationMaxError > 0.0f); + float max_error_sqr = _Data->CurveTessellationMaxError * _Data->CurveTessellationMaxError; + PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, max_error_sqr, 0);// Auto-tessellated } else { diff --git a/imgui_internal.h b/imgui_internal.h index 208c04cb4..a633c8cee 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -549,8 +549,8 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry IMGUI_API ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t); -IMGUI_API ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments); // For curves with explicit number of segments -IMGUI_API ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol);// For auto-tessellated curves you can use tess_tol = style.CurveTessellationTol +IMGUI_API ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments); // For curves with explicit number of segments +IMGUI_API ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float max_error);// For auto-tessellated curves you can use max_error = style.CurveTessellationMaxError IMGUI_API ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t); IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); @@ -895,7 +895,7 @@ struct IMGUI_API ImDrawListSharedData ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for for simplified AddText overload) float FontScale; // Current font scale (== FontSize / Font->FontSize) - float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() + float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc float InitialFringeScale; // Initial scale to apply to AA fringe ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) From 5108603789af559bde144d0848da8297bbf39803 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 3 Aug 2026 19:11:05 +0200 Subject: [PATCH 160/254] DrawList: apply current pixel density to DrawList's fringe scale. Added ImDrawList::_SetPixelDensity(). Reword FontRasterizerDensity as CurrentPixelDensity. Amend 94a4322. # Conflicts: # imgui.cpp # imgui.h # imgui_draw.cpp # imgui_internal.h --- docs/CHANGELOG.txt | 6 +++++- imgui.cpp | 17 +++++++++-------- imgui.h | 4 +++- imgui_draw.cpp | 14 ++++++++++---- imgui_internal.h | 7 +++---- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 69aa3477c..93dc4c9f7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -51,7 +51,11 @@ Other Changes: - Fonts: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of - ProggyClean on most Mac setups by default. + ProggyClean on most Apple/Retina setups by default. +- ImDrawList: + - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: + - AA Fringe is scaled accordingly. + - Circle and Curves tessellation error are scaled accordingly. ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index b1791da0e..2eee9d87f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4240,7 +4240,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) Font = NULL; FontBaked = NULL; FontSize = FontSizeBase = FontBakedScale = CurrentDpiScale = 0.0f; - FontRasterizerDensity = 1.0f; + CurrentPixelDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); if (shared_font_atlas == NULL) IO.Fonts->OwnerContext = this; @@ -4711,7 +4711,7 @@ static void SetCurrentWindow(ImGuiWindow* window) if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) { ImGuiViewport* viewport = window->Viewport; - g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() + g.CurrentPixelDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetPixelDensity() } const bool backup_skip_items = window->SkipItems; window->SkipItems = false; @@ -5308,6 +5308,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw if (viewport->BgFgDrawListsLastTimeActive[drawlist_no] != (float)g.Time) { draw_list->_ResetForNewFrame(); + draw_list->_SetPixelDensity(viewport->FramebufferScale.x); draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastTimeActive[drawlist_no] = (float)g.Time; @@ -5591,7 +5592,6 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; - g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments. } void ImGui::NewFrame() @@ -8081,6 +8081,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); + window->DrawList->_SetPixelDensity(window->Viewport->FramebufferScale.x); window->DrawList->PushTexture(g.Font->OwnerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); @@ -9049,7 +9050,7 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) // - UnregisterFontAtlas() [Internal] // - SetCurrentFont() [Internal] // - UpdateCurrentFontSize() [Internal] -// - SetFontRasterizerDensity() [Internal] +// - SetPixelDensity() [Internal] // - PushFont() // - PopFont() //----------------------------------------------------------------------------- @@ -9242,7 +9243,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) final_size = GetRoundedFontSize(final_size); final_size = ImClamp(final_size, 1.0f, IMGUI_FONT_SIZE_MAX); if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) - g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.Font->CurrentRasterizerDensity = g.CurrentPixelDensity; g.FontSize = final_size; g.DrawListSharedData.FontSize = g.FontSize; @@ -9266,13 +9267,13 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) // Exposed in case user may want to override setting density. // IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. -void ImGui::SetFontRasterizerDensity(float rasterizer_density) +void ImGui::SetPixelDensity(float pixel_density) { ImGuiContext& g = *GImGui; IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures); - if (g.FontRasterizerDensity == rasterizer_density) + if (g.CurrentPixelDensity == pixel_density) return; - g.FontRasterizerDensity = rasterizer_density; + g.CurrentPixelDensity = pixel_density; UpdateCurrentFontSize(0.0f); } diff --git a/imgui.h b/imgui.h index 4bf5fe38b..fea82515d 100644 --- a/imgui.h +++ b/imgui.h @@ -3346,7 +3346,8 @@ struct ImDrawList ImVector _ClipRectStack; // [Internal] ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] - float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content + float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content. + float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity. const char* _OwnerName; // Pointer to owner window's name for debugging // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). @@ -3480,6 +3481,7 @@ struct ImDrawList // [Internal helpers] IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); + IMGUI_API void _SetPixelDensity(float pixel_density); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); IMGUI_API void _TryMergeDrawCmds(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a84fc3ef1..a9759c072 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -396,7 +396,6 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) ImDrawListSharedData::ImDrawListSharedData() { memset((void*)this, 0, sizeof(*this)); - InitialFringeScale = 1.0f; for (int i = 0; i < IM_COUNTOF(ArcFastVtx); i++) { const float a = ((float)i * 2 * IM_PI) / (float)IM_COUNTOF(ArcFastVtx); @@ -473,7 +472,7 @@ void ImDrawList::_ResetForNewFrame() _Path.resize(0); _Splitter.Clear(); CmdBuffer.push_back(ImDrawCmd()); - _FringeScale = _Data->InitialFringeScale; + // Caller needs to bet _SetPixelDensity() as well. } void ImDrawList::_ClearFreeMemory() @@ -655,6 +654,7 @@ void ImDrawList::_OnChangedVtxOffset() int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const { // Automatic segment count + radius *= _InvFringeScale; const int radius_idx = (int)(radius + 0.999f); // ceil to never reduce accuracy if (radius_idx >= 0 && radius_idx < IM_COUNTOF(_Data->CircleSegmentCounts)) return _Data->CircleSegmentCounts[radius_idx]; // Use cached value @@ -720,6 +720,12 @@ void ImDrawList::_SetTexture(ImTextureRef tex_ref) _OnChangedTexture(); } +void ImDrawList::_SetPixelDensity(float pixel_density) +{ + _FringeScale = 1.0f / pixel_density; + _InvFringeScale = pixel_density; +} + // Reserve space for a number of vertices and indices. // You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or // submit the intermediate results. PrimUnreserve() can be used to release unused allocations. @@ -1419,7 +1425,7 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons if (num_segments == 0) { IM_ASSERT(_Data->CurveTessellationMaxError > 0.0f); - float max_error_sqr = _Data->CurveTessellationMaxError * _Data->CurveTessellationMaxError; + float max_error_sqr = (_Data->CurveTessellationMaxError * _FringeScale) * (_Data->CurveTessellationMaxError * _FringeScale); PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, max_error_sqr, 0); // Auto-tessellated } else @@ -1436,7 +1442,7 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, if (num_segments == 0) { IM_ASSERT(_Data->CurveTessellationMaxError > 0.0f); - float max_error_sqr = _Data->CurveTessellationMaxError * _Data->CurveTessellationMaxError; + float max_error_sqr = (_Data->CurveTessellationMaxError * _FringeScale) * (_Data->CurveTessellationMaxError * _FringeScale); PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, max_error_sqr, 0);// Auto-tessellated } else diff --git a/imgui_internal.h b/imgui_internal.h index a633c8cee..59f005508 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -897,7 +897,6 @@ struct IMGUI_API ImDrawListSharedData float FontScale; // Current font scale (== FontSize / Font->FontSize) float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc - float InitialFringeScale; // Initial scale to apply to AA fringe ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer @@ -2267,7 +2266,7 @@ struct ImGuiContext float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function). float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() when specified. float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. - float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). + float CurrentPixelDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; ImGuiID WithinEndChildID; // Set within EndChild() @@ -3390,8 +3389,8 @@ namespace ImGui IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling); IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling); - IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); - inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } + IMGUI_API void SetPixelDensity(float pixel_density); // was SetFontRasterizerDensity() + inline float GetPixelDensity() { return GImGui->CurrentPixelDensity; } inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } IMGUI_API ImFont* GetDefaultFont(); IMGUI_API void PushPasswordFont(); From 40ec568762a2adc4a8680ebd5acd27be5fe92943 Mon Sep 17 00:00:00 2001 From: Mike Gorchak Date: Wed, 29 Jul 2026 10:58:34 -0400 Subject: [PATCH 161/254] Backends+Examples: QNX Screen: added initial QNX support to backends and examples. (#9492) --- backends/imgui_impl_qnx.cpp | 763 ++++++++++++++++++ backends/imgui_impl_qnx.h | 36 + docs/BACKENDS.md | 1 + docs/CHANGELOG.txt | 4 + docs/EXAMPLES.md | 10 + examples/example_qnx_opengl3/.gitignore | 19 + examples/example_qnx_opengl3/Makefile | 2 + examples/example_qnx_opengl3/README.md | 29 + examples/example_qnx_opengl3/common.mk | 31 + .../example_qnx_opengl3.use | 6 + examples/example_qnx_opengl3/main.cpp | 299 +++++++ examples/example_qnx_opengl3/nto/Makefile | 8 + .../example_qnx_opengl3/nto/aarch64/Makefile | 8 + .../nto/aarch64/o.le/Makefile | 1 + .../example_qnx_opengl3/nto/x86_64/Makefile | 8 + .../example_qnx_opengl3/nto/x86_64/o/Makefile | 1 + examples/example_qnx_vulkan/.gitignore | 19 + examples/example_qnx_vulkan/Makefile | 2 + examples/example_qnx_vulkan/README.md | 29 + examples/example_qnx_vulkan/common.mk | 31 + .../example_qnx_vulkan/example_qnx_vulkan.use | 7 + examples/example_qnx_vulkan/main.cpp | 626 ++++++++++++++ examples/example_qnx_vulkan/nto/Makefile | 8 + .../example_qnx_vulkan/nto/aarch64/Makefile | 8 + .../nto/aarch64/o.le/Makefile | 1 + .../example_qnx_vulkan/nto/x86_64/Makefile | 8 + .../example_qnx_vulkan/nto/x86_64/o/Makefile | 1 + 27 files changed, 1966 insertions(+) create mode 100644 backends/imgui_impl_qnx.cpp create mode 100644 backends/imgui_impl_qnx.h create mode 100644 examples/example_qnx_opengl3/.gitignore create mode 100644 examples/example_qnx_opengl3/Makefile create mode 100644 examples/example_qnx_opengl3/README.md create mode 100644 examples/example_qnx_opengl3/common.mk create mode 100644 examples/example_qnx_opengl3/example_qnx_opengl3.use create mode 100644 examples/example_qnx_opengl3/main.cpp create mode 100644 examples/example_qnx_opengl3/nto/Makefile create mode 100644 examples/example_qnx_opengl3/nto/aarch64/Makefile create mode 100644 examples/example_qnx_opengl3/nto/aarch64/o.le/Makefile create mode 100644 examples/example_qnx_opengl3/nto/x86_64/Makefile create mode 100644 examples/example_qnx_opengl3/nto/x86_64/o/Makefile create mode 100644 examples/example_qnx_vulkan/.gitignore create mode 100644 examples/example_qnx_vulkan/Makefile create mode 100644 examples/example_qnx_vulkan/README.md create mode 100644 examples/example_qnx_vulkan/common.mk create mode 100644 examples/example_qnx_vulkan/example_qnx_vulkan.use create mode 100644 examples/example_qnx_vulkan/main.cpp create mode 100644 examples/example_qnx_vulkan/nto/Makefile create mode 100644 examples/example_qnx_vulkan/nto/aarch64/Makefile create mode 100644 examples/example_qnx_vulkan/nto/aarch64/o.le/Makefile create mode 100644 examples/example_qnx_vulkan/nto/x86_64/Makefile create mode 100644 examples/example_qnx_vulkan/nto/x86_64/o/Makefile diff --git a/backends/imgui_impl_qnx.cpp b/backends/imgui_impl_qnx.cpp new file mode 100644 index 000000000..82c9cb376 --- /dev/null +++ b/backends/imgui_impl_qnx.cpp @@ -0,0 +1,763 @@ +// dear imgui: Platform Backend for QNX Screen +// This needs to be used along with a Renderer Backend (e.g. OpenGL3, Vulkan). + +// Implemented features: +// [X] Platform: Keyboard support. Uses io.AddKeyEvent() and native QNX key symbols. +// [X] Platform: Mouse support. Handles position, three buttons and horizontal/vertical wheels. +// [X] Platform: Touch support. Maps the first active touch contact to the primary mouse button. +// [X] Platform: Focus and window-size updates. +// Missing features or Issues: +// [ ] Platform: Clipboard support. +// [ ] Platform: Gamepad support. +// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). +// [ ] Platform: Multi-viewport support. + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2026-07-30: Renamed backend from imgui_impl_screen to imgui_impl_qnx and embedded a compact keysym table. +// 2026-07-29: Reworked keyboard handling from the proven qterm Screen path: authoritative modifier masks, +// complete keypad/Hyper mapping, and QNX/X11 keysym-to-Unicode conversion. +// 2026-07-29: Fixed keyboard handling: use KEY_CAP for physical keys, SYM for text, and honor Screen validity flags. +// 2026-07-28: Initial QNX Screen backend. + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_impl_qnx.h" + +#include +#include +#include +#include +#include + +static unsigned int ImGui_ImplQNX_KeySymToUnicode(unsigned int keysym); + +struct ImGui_ImplQNX_Data +{ + screen_context_t Context; + screen_window_t Window; + uint64_t TimeNanoseconds; + int MouseButtons; + int ActiveTouchId; + bool LeftSuper; + bool RightSuper; + + ImGui_ImplQNX_Data() + { + memset(this, 0, sizeof(*this)); + ActiveTouchId = -1; + } +}; + +static ImGui_ImplQNX_Data* ImGui_ImplQNX_GetBackendData() +{ + return ImGui::GetCurrentContext() != nullptr ? (ImGui_ImplQNX_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; +} + +static uint64_t ImGui_ImplQNX_GetTimeNanoseconds() +{ + struct timespec timestamp = {}; + if (clock_gettime(CLOCK_MONOTONIC, ×tamp) != 0) + { + return 0; + } + return (uint64_t)timestamp.tv_sec * UINT64_C(1000000000) + (uint64_t)timestamp.tv_nsec; +} + +static ImGuiKey ImGui_ImplQNX_KeyCodeToImGuiKey(int key_code) +{ + switch (key_code) + { + case KEYCODE_TAB: return ImGuiKey_Tab; + case KEYCODE_LEFT: return ImGuiKey_LeftArrow; + case KEYCODE_RIGHT: return ImGuiKey_RightArrow; + case KEYCODE_UP: return ImGuiKey_UpArrow; + case KEYCODE_DOWN: return ImGuiKey_DownArrow; + case KEYCODE_PG_UP: return ImGuiKey_PageUp; + case KEYCODE_PG_DOWN: return ImGuiKey_PageDown; + case KEYCODE_HOME: return ImGuiKey_Home; + case KEYCODE_END: return ImGuiKey_End; + case KEYCODE_INSERT: return ImGuiKey_Insert; + case KEYCODE_DELETE: return ImGuiKey_Delete; + case KEYCODE_BACKSPACE: return ImGuiKey_Backspace; + case KEYCODE_SPACE: return ImGuiKey_Space; + case KEYCODE_RETURN: return ImGuiKey_Enter; + case KEYCODE_ESCAPE: return ImGuiKey_Escape; + case KEYCODE_APOSTROPHE: return ImGuiKey_Apostrophe; + case KEYCODE_COMMA: return ImGuiKey_Comma; + case KEYCODE_MINUS: return ImGuiKey_Minus; + case KEYCODE_PERIOD: return ImGuiKey_Period; + case KEYCODE_SLASH: return ImGuiKey_Slash; + case KEYCODE_SEMICOLON: return ImGuiKey_Semicolon; + case KEYCODE_EQUAL: return ImGuiKey_Equal; + case KEYCODE_LEFT_BRACKET: return ImGuiKey_LeftBracket; + case KEYCODE_BACK_SLASH: return ImGuiKey_Backslash; + case KEYCODE_RIGHT_BRACKET: return ImGuiKey_RightBracket; + case KEYCODE_GRAVE: return ImGuiKey_GraveAccent; + case KEYCODE_CAPS_LOCK: return ImGuiKey_CapsLock; + case KEYCODE_SCROLL_LOCK: return ImGuiKey_ScrollLock; + case KEYCODE_NUM_LOCK: return ImGuiKey_NumLock; + case KEYCODE_PRINT: return ImGuiKey_PrintScreen; + case KEYCODE_PAUSE: return ImGuiKey_Pause; + case KEYCODE_KP_DIVIDE: return ImGuiKey_KeypadDivide; + case KEYCODE_KP_MULTIPLY: return ImGuiKey_KeypadMultiply; + case KEYCODE_KP_MINUS: return ImGuiKey_KeypadSubtract; + case KEYCODE_KP_PLUS: return ImGuiKey_KeypadAdd; + case KEYCODE_KP_ENTER: return ImGuiKey_KeypadEnter; + // QNX reports keypad digits by their navigation key-cap names. + case KEYCODE_KP_INSERT: return ImGuiKey_Keypad0; + case KEYCODE_KP_END: return ImGuiKey_Keypad1; + case KEYCODE_KP_DOWN: return ImGuiKey_Keypad2; + case KEYCODE_KP_PG_DOWN: return ImGuiKey_Keypad3; + case KEYCODE_KP_LEFT: return ImGuiKey_Keypad4; + case KEYCODE_KP_FIVE: return ImGuiKey_Keypad5; + case KEYCODE_KP_RIGHT: return ImGuiKey_Keypad6; + case KEYCODE_KP_HOME: return ImGuiKey_Keypad7; + case KEYCODE_KP_UP: return ImGuiKey_Keypad8; + case KEYCODE_KP_PG_UP: return ImGuiKey_Keypad9; + case KEYCODE_KP_DELETE: return ImGuiKey_KeypadDecimal; + case KEYCODE_LEFT_CTRL: return ImGuiKey_LeftCtrl; + case KEYCODE_LEFT_SHIFT: return ImGuiKey_LeftShift; + case KEYCODE_LEFT_ALT: return ImGuiKey_LeftAlt; + case KEYCODE_RIGHT_CTRL: return ImGuiKey_RightCtrl; + case KEYCODE_RIGHT_SHIFT: return ImGuiKey_RightShift; + case KEYCODE_RIGHT_ALT: return ImGuiKey_RightAlt; + case KEYCODE_LEFT_HYPER: return ImGuiKey_LeftSuper; + case KEYCODE_RIGHT_HYPER: return ImGuiKey_RightSuper; + case KEYCODE_MENU: return ImGuiKey_Menu; + case KEYCODE_ZERO: return ImGuiKey_0; + case KEYCODE_ONE: return ImGuiKey_1; + case KEYCODE_TWO: return ImGuiKey_2; + case KEYCODE_THREE: return ImGuiKey_3; + case KEYCODE_FOUR: return ImGuiKey_4; + case KEYCODE_FIVE: return ImGuiKey_5; + case KEYCODE_SIX: return ImGuiKey_6; + case KEYCODE_SEVEN: return ImGuiKey_7; + case KEYCODE_EIGHT: return ImGuiKey_8; + case KEYCODE_NINE: return ImGuiKey_9; + case KEYCODE_A: return ImGuiKey_A; + case KEYCODE_B: return ImGuiKey_B; + case KEYCODE_C: return ImGuiKey_C; + case KEYCODE_D: return ImGuiKey_D; + case KEYCODE_E: return ImGuiKey_E; + case KEYCODE_F: return ImGuiKey_F; + case KEYCODE_G: return ImGuiKey_G; + case KEYCODE_H: return ImGuiKey_H; + case KEYCODE_I: return ImGuiKey_I; + case KEYCODE_J: return ImGuiKey_J; + case KEYCODE_K: return ImGuiKey_K; + case KEYCODE_L: return ImGuiKey_L; + case KEYCODE_M: return ImGuiKey_M; + case KEYCODE_N: return ImGuiKey_N; + case KEYCODE_O: return ImGuiKey_O; + case KEYCODE_P: return ImGuiKey_P; + case KEYCODE_Q: return ImGuiKey_Q; + case KEYCODE_R: return ImGuiKey_R; + case KEYCODE_S: return ImGuiKey_S; + case KEYCODE_T: return ImGuiKey_T; + case KEYCODE_U: return ImGuiKey_U; + case KEYCODE_V: return ImGuiKey_V; + case KEYCODE_W: return ImGuiKey_W; + case KEYCODE_X: return ImGuiKey_X; + case KEYCODE_Y: return ImGuiKey_Y; + case KEYCODE_Z: return ImGuiKey_Z; + case KEYCODE_F1: return ImGuiKey_F1; + case KEYCODE_F2: return ImGuiKey_F2; + case KEYCODE_F3: return ImGuiKey_F3; + case KEYCODE_F4: return ImGuiKey_F4; + case KEYCODE_F5: return ImGuiKey_F5; + case KEYCODE_F6: return ImGuiKey_F6; + case KEYCODE_F7: return ImGuiKey_F7; + case KEYCODE_F8: return ImGuiKey_F8; + case KEYCODE_F9: return ImGuiKey_F9; + case KEYCODE_F10: return ImGuiKey_F10; + case KEYCODE_F11: return ImGuiKey_F11; + case KEYCODE_F12: return ImGuiKey_F12; + default: return ImGuiKey_None; + } +} + +static void ImGui_ImplQNX_UpdateKeyModifiers(ImGuiIO& io, ImGui_ImplQNX_Data* bd, int modifiers, ImGuiKey key, bool down) +{ + // SCREEN_PROPERTY_MODIFIERS is the authoritative state for Shift/Ctrl/Alt. + // Tracking those keys locally loses sticky modifiers and can become stale if + // a key transition is consumed elsewhere. qterm uses this same Screen state. + if (key == ImGuiKey_LeftSuper) + { + bd->LeftSuper = down; + } + else if (key == ImGuiKey_RightSuper) + { + bd->RightSuper = down; + } + + io.AddKeyEvent(ImGuiMod_Ctrl, (modifiers & KEYMOD_CTRL) != 0); + io.AddKeyEvent(ImGuiMod_Shift, (modifiers & KEYMOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiMod_Alt, (modifiers & KEYMOD_ALT) != 0); + io.AddKeyEvent(ImGuiMod_Super, bd->LeftSuper || bd->RightSuper); +} + +static void ImGui_ImplQNX_ClearModifierState(ImGuiIO& io, ImGui_ImplQNX_Data* bd) +{ + bd->LeftSuper = false; + bd->RightSuper = false; + io.AddKeyEvent(ImGuiMod_Ctrl, false); + io.AddKeyEvent(ImGuiMod_Shift, false); + io.AddKeyEvent(ImGuiMod_Alt, false); + io.AddKeyEvent(ImGuiMod_Super, false); +} + +static bool ImGui_ImplQNX_IsTextCodePoint(unsigned int code_point) +{ + // At this point the QNX keysym has already been converted to Unicode. + // Do not reject Unicode private-use characters: a user keymap may emit one. + const bool surrogate = code_point >= 0xd800u && code_point <= 0xdfffu; + const bool control = code_point < 0x20u || (code_point >= 0x7fu && code_point <= 0x9fu); + return code_point <= IM_UNICODE_CODEPOINT_MAX && !surrogate && !control; +} + +static bool ImGui_ImplQNX_ProcessKeyboardEvent(ImGui_ImplQNX_Data* bd, screen_event_t event) +{ + int flags = 0; + int modifiers = 0; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_FLAGS, &flags) != 0 || + screen_get_event_property_iv(event, SCREEN_PROPERTY_MODIFIERS, &modifiers) != 0) + { + return false; + } + + // KEY_CAP is the unmodified key identity used for ImGuiKey events. SYM is + // the layout/modifier-translated QNX keysym used for text input. + int key_cap = 0; + int key_sym = 0; + int scan_code = 0; + const bool cap_valid = (flags & SCREEN_FLAG_CAP_VALID) != 0; + const bool sym_valid = (flags & SCREEN_FLAG_SYM_VALID) != 0; + const bool scan_valid = (flags & SCREEN_FLAG_SCAN_VALID) != 0; + const bool down = (flags & (SCREEN_FLAG_KEY_DOWN | SCREEN_FLAG_KEY_REPEAT)) != 0; + + if (cap_valid && screen_get_event_property_iv(event, SCREEN_PROPERTY_KEY_CAP, &key_cap) != 0) + { + return false; + } + if (sym_valid && screen_get_event_property_iv(event, SCREEN_PROPERTY_SYM, &key_sym) != 0) + { + return false; + } + if (scan_valid && screen_get_event_property_iv(event, SCREEN_PROPERTY_SCAN, &scan_code) != 0) + { + scan_code = 0; + } + + const ImGuiKey key = cap_valid ? ImGui_ImplQNX_KeyCodeToImGuiKey(key_cap) : ImGuiKey_None; + ImGuiIO& io = ImGui::GetIO(); + + // Modifier events must precede the ordinary key event in Dear ImGui's queue. + // Unlike qterm, we still submit the individual modifier key because ImGui + // exposes Left/Right Ctrl, Shift, Alt and Super as named keys. + ImGui_ImplQNX_UpdateKeyModifiers(io, bd, modifiers, key, down); + + if (key != ImGuiKey_None) + { + io.AddKeyEvent(key, down); + io.SetKeyEventNativeData(key, key_cap, scan_code); + } + + bool text_added = false; + if (down && sym_valid && key_sym >= 0) + { + const unsigned int keysym = (unsigned int)key_sym; + // QNX special/function symbols begin at KEYCODE_PC_KEYS. Printable + // symbols below that boundary use X11 keysym encoding. Also accept the + // standard directly encoded UCS keysym form (0x01000000 | codepoint). + if (keysym < (unsigned int)KEYCODE_PC_KEYS || (keysym & 0xff000000u) == 0x01000000u) + { + const unsigned int code_point = ImGui_ImplQNX_KeySymToUnicode(keysym); + if (ImGui_ImplQNX_IsTextCodePoint(code_point)) + { + io.AddInputCharacter(code_point); + text_added = true; + } + } + } + + return key != ImGuiKey_None || text_added || cap_valid || sym_valid; +} + +static bool ImGui_ImplQNX_ProcessPointerEvent(ImGui_ImplQNX_Data* bd, screen_event_t event) +{ + int position[2] = {}; + int buttons = 0; + int wheel_vertical = 0; + int wheel_horizontal = 0; + + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_SOURCE_POSITION, position) != 0 && + screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, position) != 0) + { + return false; + } + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_BUTTONS, &buttons) != 0) + { + buttons = bd->MouseButtons; + } + + ImGuiIO& io = ImGui::GetIO(); + io.AddMouseSourceEvent(ImGuiMouseSource_Mouse); + io.AddMousePosEvent((float)position[0], (float)position[1]); + + const int changed_buttons = buttons ^ bd->MouseButtons; + if ((changed_buttons & SCREEN_LEFT_MOUSE_BUTTON) != 0) + { + io.AddMouseButtonEvent(0, (buttons & SCREEN_LEFT_MOUSE_BUTTON) != 0); + } + if ((changed_buttons & SCREEN_RIGHT_MOUSE_BUTTON) != 0) + { + io.AddMouseButtonEvent(1, (buttons & SCREEN_RIGHT_MOUSE_BUTTON) != 0); + } + if ((changed_buttons & SCREEN_MIDDLE_MOUSE_BUTTON) != 0) + { + io.AddMouseButtonEvent(2, (buttons & SCREEN_MIDDLE_MOUSE_BUTTON) != 0); + } + bd->MouseButtons = buttons; + + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_MOUSE_WHEEL, &wheel_vertical) != 0) + { + wheel_vertical = 0; + } + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_MOUSE_HORIZONTAL_WHEEL, &wheel_horizontal) != 0) + { + wheel_horizontal = 0; + } + if (wheel_vertical != 0 || wheel_horizontal != 0) + { + // Screen: up/left are negative. Dear ImGui: up/left are positive. + io.AddMouseWheelEvent((float)-wheel_horizontal, (float)-wheel_vertical); + } + return true; +} + +static bool ImGui_ImplQNX_ProcessTouchEvent(ImGui_ImplQNX_Data* bd, screen_event_t event, int event_type) +{ + int touch_id = -1; + int position[2] = {}; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_TOUCH_ID, &touch_id) != 0) + { + return false; + } + + const bool is_release = event_type == SCREEN_EVENT_MTOUCH_RELEASE; + if (bd->ActiveTouchId != -1 && touch_id != bd->ActiveTouchId) + { + return false; + } + + ImGuiIO& io = ImGui::GetIO(); + io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); + + if (!is_release) + { + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_SOURCE_POSITION, position) != 0 && + screen_get_event_property_iv(event, SCREEN_PROPERTY_POSITION, position) != 0) + { + return false; + } + io.AddMousePosEvent((float)position[0], (float)position[1]); + } + + if (event_type == SCREEN_EVENT_MTOUCH_TOUCH) + { + bd->ActiveTouchId = touch_id; + io.AddMouseButtonEvent(0, true); + } + else if (is_release) + { + io.AddMouseButtonEvent(0, false); + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + bd->ActiveTouchId = -1; + } + return true; +} + +bool ImGui_ImplQNX_ProcessEvent(screen_event_t event) +{ + ImGui_ImplQNX_Data* bd = ImGui_ImplQNX_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplQNX_Init()?"); + if (bd == nullptr || event == nullptr) + { + return false; + } + + int event_type = SCREEN_EVENT_NONE; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_TYPE, &event_type) != 0 || event_type == SCREEN_EVENT_NONE) + { + return false; + } + switch (event_type) + { + case SCREEN_EVENT_KEYBOARD: + return ImGui_ImplQNX_ProcessKeyboardEvent(bd, event); + case SCREEN_EVENT_POINTER: + return ImGui_ImplQNX_ProcessPointerEvent(bd, event); + case SCREEN_EVENT_MTOUCH_TOUCH: + case SCREEN_EVENT_MTOUCH_MOVE: + case SCREEN_EVENT_MTOUCH_RELEASE: + return ImGui_ImplQNX_ProcessTouchEvent(bd, event, event_type); + case SCREEN_EVENT_PROPERTY: + { + int property = 0; + if (screen_get_event_property_iv(event, SCREEN_PROPERTY_NAME, &property) != 0) + { + return false; + } + if (property == SCREEN_PROPERTY_FOCUS) + { + int focused = 0; + if (screen_get_window_property_iv(bd->Window, SCREEN_PROPERTY_FOCUS, &focused) == 0) + { + ImGuiIO& io = ImGui::GetIO(); + io.AddFocusEvent(focused != 0); + if (focused == 0) + { + ImGui_ImplQNX_ClearModifierState(io, bd); + } + } + return true; + } + if (property == SCREEN_PROPERTY_SIZE || property == SCREEN_PROPERTY_BUFFER_SIZE || property == SCREEN_PROPERTY_VISIBLE) + { + return true; + } + return false; + } + case SCREEN_EVENT_CLOSE: + { + ImGuiIO& io = ImGui::GetIO(); + io.AddFocusEvent(false); + ImGui_ImplQNX_ClearModifierState(io, bd); + return true; + } + default: + return false; + } +} + +bool ImGui_ImplQNX_Init(screen_context_t context, screen_window_t window) +{ + ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); + IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); + IM_ASSERT(context != nullptr && window != nullptr); + if (context == nullptr || window == nullptr) + { + return false; + } + + ImGui_ImplQNX_Data* bd = IM_NEW(ImGui_ImplQNX_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_qnx"; + + bd->Context = context; + bd->Window = window; + bd->TimeNanoseconds = ImGui_ImplQNX_GetTimeNanoseconds(); + + int focused = 0; + if (screen_get_window_property_iv(window, SCREEN_PROPERTY_FOCUS, &focused) == 0) + { + io.AddFocusEvent(focused != 0); + } + return true; +} + +void ImGui_ImplQNX_Shutdown() +{ + ImGui_ImplQNX_Data* bd = ImGui_ImplQNX_GetBackendData(); + IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); + if (bd == nullptr) + { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + io.BackendPlatformName = nullptr; + io.BackendPlatformUserData = nullptr; + IM_DELETE(bd); +} + +void ImGui_ImplQNX_NewFrame() +{ + ImGui_ImplQNX_Data* bd = ImGui_ImplQNX_GetBackendData(); + IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplQNX_Init()?"); + if (bd == nullptr) + { + return; + } + + ImGuiIO& io = ImGui::GetIO(); + int size[2] = {}; + if (screen_get_window_property_iv(bd->Window, SCREEN_PROPERTY_BUFFER_SIZE, size) != 0 || + size[0] <= 0 || size[1] <= 0) + { + size[0] = 0; + size[1] = 0; + (void)screen_get_window_property_iv(bd->Window, SCREEN_PROPERTY_SIZE, size); + } + if (size[0] > 0 && size[1] > 0) + { + io.DisplaySize = ImVec2((float)size[0], (float)size[1]); + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + } + + const uint64_t current_time = ImGui_ImplQNX_GetTimeNanoseconds(); + if (current_time > bd->TimeNanoseconds && bd->TimeNanoseconds != 0) + { + io.DeltaTime = (float)((double)(current_time - bd->TimeNanoseconds) / 1000000000.0); + } + else + { + io.DeltaTime = 1.0f / 60.0f; + } + bd->TimeNanoseconds = current_time; +} + +// Generated by Markus Kuhn's public-domain unicode/convmap.pl from X11 keysymdef.h and UnicodeData. +// Original source: https://www.cl.cam.ac.uk/~mgk25/ucs/keysym2ucs.c +// The table must remain sorted by keysym for the binary search below. +struct ImGui_ImplQNX_KeySymPair +{ + uint16_t KeySym; + uint16_t Unicode; +}; + +static const ImGui_ImplQNX_KeySymPair ImGui_ImplQNX_KeySymTable[] = +{ + { 0x01a1, 0x0104 }, { 0x01a2, 0x02d8 }, { 0x01a3, 0x0141 }, { 0x01a5, 0x013d }, + { 0x01a6, 0x015a }, { 0x01a9, 0x0160 }, { 0x01aa, 0x015e }, { 0x01ab, 0x0164 }, + { 0x01ac, 0x0179 }, { 0x01ae, 0x017d }, { 0x01af, 0x017b }, { 0x01b1, 0x0105 }, + { 0x01b2, 0x02db }, { 0x01b3, 0x0142 }, { 0x01b5, 0x013e }, { 0x01b6, 0x015b }, + { 0x01b7, 0x02c7 }, { 0x01b9, 0x0161 }, { 0x01ba, 0x015f }, { 0x01bb, 0x0165 }, + { 0x01bc, 0x017a }, { 0x01bd, 0x02dd }, { 0x01be, 0x017e }, { 0x01bf, 0x017c }, + { 0x01c0, 0x0154 }, { 0x01c3, 0x0102 }, { 0x01c5, 0x0139 }, { 0x01c6, 0x0106 }, + { 0x01c8, 0x010c }, { 0x01ca, 0x0118 }, { 0x01cc, 0x011a }, { 0x01cf, 0x010e }, + { 0x01d0, 0x0110 }, { 0x01d1, 0x0143 }, { 0x01d2, 0x0147 }, { 0x01d5, 0x0150 }, + { 0x01d8, 0x0158 }, { 0x01d9, 0x016e }, { 0x01db, 0x0170 }, { 0x01de, 0x0162 }, + { 0x01e0, 0x0155 }, { 0x01e3, 0x0103 }, { 0x01e5, 0x013a }, { 0x01e6, 0x0107 }, + { 0x01e8, 0x010d }, { 0x01ea, 0x0119 }, { 0x01ec, 0x011b }, { 0x01ef, 0x010f }, + { 0x01f0, 0x0111 }, { 0x01f1, 0x0144 }, { 0x01f2, 0x0148 }, { 0x01f5, 0x0151 }, + { 0x01f8, 0x0159 }, { 0x01f9, 0x016f }, { 0x01fb, 0x0171 }, { 0x01fe, 0x0163 }, + { 0x01ff, 0x02d9 }, { 0x02a1, 0x0126 }, { 0x02a6, 0x0124 }, { 0x02a9, 0x0130 }, + { 0x02ab, 0x011e }, { 0x02ac, 0x0134 }, { 0x02b1, 0x0127 }, { 0x02b6, 0x0125 }, + { 0x02b9, 0x0131 }, { 0x02bb, 0x011f }, { 0x02bc, 0x0135 }, { 0x02c5, 0x010a }, + { 0x02c6, 0x0108 }, { 0x02d5, 0x0120 }, { 0x02d8, 0x011c }, { 0x02dd, 0x016c }, + { 0x02de, 0x015c }, { 0x02e5, 0x010b }, { 0x02e6, 0x0109 }, { 0x02f5, 0x0121 }, + { 0x02f8, 0x011d }, { 0x02fd, 0x016d }, { 0x02fe, 0x015d }, { 0x03a2, 0x0138 }, + { 0x03a3, 0x0156 }, { 0x03a5, 0x0128 }, { 0x03a6, 0x013b }, { 0x03aa, 0x0112 }, + { 0x03ab, 0x0122 }, { 0x03ac, 0x0166 }, { 0x03b3, 0x0157 }, { 0x03b5, 0x0129 }, + { 0x03b6, 0x013c }, { 0x03ba, 0x0113 }, { 0x03bb, 0x0123 }, { 0x03bc, 0x0167 }, + { 0x03bd, 0x014a }, { 0x03bf, 0x014b }, { 0x03c0, 0x0100 }, { 0x03c7, 0x012e }, + { 0x03cc, 0x0116 }, { 0x03cf, 0x012a }, { 0x03d1, 0x0145 }, { 0x03d2, 0x014c }, + { 0x03d3, 0x0136 }, { 0x03d9, 0x0172 }, { 0x03dd, 0x0168 }, { 0x03de, 0x016a }, + { 0x03e0, 0x0101 }, { 0x03e7, 0x012f }, { 0x03ec, 0x0117 }, { 0x03ef, 0x012b }, + { 0x03f1, 0x0146 }, { 0x03f2, 0x014d }, { 0x03f3, 0x0137 }, { 0x03f9, 0x0173 }, + { 0x03fd, 0x0169 }, { 0x03fe, 0x016b }, { 0x047e, 0x203e }, { 0x04a1, 0x3002 }, + { 0x04a2, 0x300c }, { 0x04a3, 0x300d }, { 0x04a4, 0x3001 }, { 0x04a5, 0x30fb }, + { 0x04a6, 0x30f2 }, { 0x04a7, 0x30a1 }, { 0x04a8, 0x30a3 }, { 0x04a9, 0x30a5 }, + { 0x04aa, 0x30a7 }, { 0x04ab, 0x30a9 }, { 0x04ac, 0x30e3 }, { 0x04ad, 0x30e5 }, + { 0x04ae, 0x30e7 }, { 0x04af, 0x30c3 }, { 0x04b0, 0x30fc }, { 0x04b1, 0x30a2 }, + { 0x04b2, 0x30a4 }, { 0x04b3, 0x30a6 }, { 0x04b4, 0x30a8 }, { 0x04b5, 0x30aa }, + { 0x04b6, 0x30ab }, { 0x04b7, 0x30ad }, { 0x04b8, 0x30af }, { 0x04b9, 0x30b1 }, + { 0x04ba, 0x30b3 }, { 0x04bb, 0x30b5 }, { 0x04bc, 0x30b7 }, { 0x04bd, 0x30b9 }, + { 0x04be, 0x30bb }, { 0x04bf, 0x30bd }, { 0x04c0, 0x30bf }, { 0x04c1, 0x30c1 }, + { 0x04c2, 0x30c4 }, { 0x04c3, 0x30c6 }, { 0x04c4, 0x30c8 }, { 0x04c5, 0x30ca }, + { 0x04c6, 0x30cb }, { 0x04c7, 0x30cc }, { 0x04c8, 0x30cd }, { 0x04c9, 0x30ce }, + { 0x04ca, 0x30cf }, { 0x04cb, 0x30d2 }, { 0x04cc, 0x30d5 }, { 0x04cd, 0x30d8 }, + { 0x04ce, 0x30db }, { 0x04cf, 0x30de }, { 0x04d0, 0x30df }, { 0x04d1, 0x30e0 }, + { 0x04d2, 0x30e1 }, { 0x04d3, 0x30e2 }, { 0x04d4, 0x30e4 }, { 0x04d5, 0x30e6 }, + { 0x04d6, 0x30e8 }, { 0x04d7, 0x30e9 }, { 0x04d8, 0x30ea }, { 0x04d9, 0x30eb }, + { 0x04da, 0x30ec }, { 0x04db, 0x30ed }, { 0x04dc, 0x30ef }, { 0x04dd, 0x30f3 }, + { 0x04de, 0x309b }, { 0x04df, 0x309c }, { 0x05ac, 0x060c }, { 0x05bb, 0x061b }, + { 0x05bf, 0x061f }, { 0x05c1, 0x0621 }, { 0x05c2, 0x0622 }, { 0x05c3, 0x0623 }, + { 0x05c4, 0x0624 }, { 0x05c5, 0x0625 }, { 0x05c6, 0x0626 }, { 0x05c7, 0x0627 }, + { 0x05c8, 0x0628 }, { 0x05c9, 0x0629 }, { 0x05ca, 0x062a }, { 0x05cb, 0x062b }, + { 0x05cc, 0x062c }, { 0x05cd, 0x062d }, { 0x05ce, 0x062e }, { 0x05cf, 0x062f }, + { 0x05d0, 0x0630 }, { 0x05d1, 0x0631 }, { 0x05d2, 0x0632 }, { 0x05d3, 0x0633 }, + { 0x05d4, 0x0634 }, { 0x05d5, 0x0635 }, { 0x05d6, 0x0636 }, { 0x05d7, 0x0637 }, + { 0x05d8, 0x0638 }, { 0x05d9, 0x0639 }, { 0x05da, 0x063a }, { 0x05e0, 0x0640 }, + { 0x05e1, 0x0641 }, { 0x05e2, 0x0642 }, { 0x05e3, 0x0643 }, { 0x05e4, 0x0644 }, + { 0x05e5, 0x0645 }, { 0x05e6, 0x0646 }, { 0x05e7, 0x0647 }, { 0x05e8, 0x0648 }, + { 0x05e9, 0x0649 }, { 0x05ea, 0x064a }, { 0x05eb, 0x064b }, { 0x05ec, 0x064c }, + { 0x05ed, 0x064d }, { 0x05ee, 0x064e }, { 0x05ef, 0x064f }, { 0x05f0, 0x0650 }, + { 0x05f1, 0x0651 }, { 0x05f2, 0x0652 }, { 0x06a1, 0x0452 }, { 0x06a2, 0x0453 }, + { 0x06a3, 0x0451 }, { 0x06a4, 0x0454 }, { 0x06a5, 0x0455 }, { 0x06a6, 0x0456 }, + { 0x06a7, 0x0457 }, { 0x06a8, 0x0458 }, { 0x06a9, 0x0459 }, { 0x06aa, 0x045a }, + { 0x06ab, 0x045b }, { 0x06ac, 0x045c }, { 0x06ae, 0x045e }, { 0x06af, 0x045f }, + { 0x06b0, 0x2116 }, { 0x06b1, 0x0402 }, { 0x06b2, 0x0403 }, { 0x06b3, 0x0401 }, + { 0x06b4, 0x0404 }, { 0x06b5, 0x0405 }, { 0x06b6, 0x0406 }, { 0x06b7, 0x0407 }, + { 0x06b8, 0x0408 }, { 0x06b9, 0x0409 }, { 0x06ba, 0x040a }, { 0x06bb, 0x040b }, + { 0x06bc, 0x040c }, { 0x06be, 0x040e }, { 0x06bf, 0x040f }, { 0x06c0, 0x044e }, + { 0x06c1, 0x0430 }, { 0x06c2, 0x0431 }, { 0x06c3, 0x0446 }, { 0x06c4, 0x0434 }, + { 0x06c5, 0x0435 }, { 0x06c6, 0x0444 }, { 0x06c7, 0x0433 }, { 0x06c8, 0x0445 }, + { 0x06c9, 0x0438 }, { 0x06ca, 0x0439 }, { 0x06cb, 0x043a }, { 0x06cc, 0x043b }, + { 0x06cd, 0x043c }, { 0x06ce, 0x043d }, { 0x06cf, 0x043e }, { 0x06d0, 0x043f }, + { 0x06d1, 0x044f }, { 0x06d2, 0x0440 }, { 0x06d3, 0x0441 }, { 0x06d4, 0x0442 }, + { 0x06d5, 0x0443 }, { 0x06d6, 0x0436 }, { 0x06d7, 0x0432 }, { 0x06d8, 0x044c }, + { 0x06d9, 0x044b }, { 0x06da, 0x0437 }, { 0x06db, 0x0448 }, { 0x06dc, 0x044d }, + { 0x06dd, 0x0449 }, { 0x06de, 0x0447 }, { 0x06df, 0x044a }, { 0x06e0, 0x042e }, + { 0x06e1, 0x0410 }, { 0x06e2, 0x0411 }, { 0x06e3, 0x0426 }, { 0x06e4, 0x0414 }, + { 0x06e5, 0x0415 }, { 0x06e6, 0x0424 }, { 0x06e7, 0x0413 }, { 0x06e8, 0x0425 }, + { 0x06e9, 0x0418 }, { 0x06ea, 0x0419 }, { 0x06eb, 0x041a }, { 0x06ec, 0x041b }, + { 0x06ed, 0x041c }, { 0x06ee, 0x041d }, { 0x06ef, 0x041e }, { 0x06f0, 0x041f }, + { 0x06f1, 0x042f }, { 0x06f2, 0x0420 }, { 0x06f3, 0x0421 }, { 0x06f4, 0x0422 }, + { 0x06f5, 0x0423 }, { 0x06f6, 0x0416 }, { 0x06f7, 0x0412 }, { 0x06f8, 0x042c }, + { 0x06f9, 0x042b }, { 0x06fa, 0x0417 }, { 0x06fb, 0x0428 }, { 0x06fc, 0x042d }, + { 0x06fd, 0x0429 }, { 0x06fe, 0x0427 }, { 0x06ff, 0x042a }, { 0x07a1, 0x0386 }, + { 0x07a2, 0x0388 }, { 0x07a3, 0x0389 }, { 0x07a4, 0x038a }, { 0x07a5, 0x03aa }, + { 0x07a7, 0x038c }, { 0x07a8, 0x038e }, { 0x07a9, 0x03ab }, { 0x07ab, 0x038f }, + { 0x07ae, 0x0385 }, { 0x07af, 0x2015 }, { 0x07b1, 0x03ac }, { 0x07b2, 0x03ad }, + { 0x07b3, 0x03ae }, { 0x07b4, 0x03af }, { 0x07b5, 0x03ca }, { 0x07b6, 0x0390 }, + { 0x07b7, 0x03cc }, { 0x07b8, 0x03cd }, { 0x07b9, 0x03cb }, { 0x07ba, 0x03b0 }, + { 0x07bb, 0x03ce }, { 0x07c1, 0x0391 }, { 0x07c2, 0x0392 }, { 0x07c3, 0x0393 }, + { 0x07c4, 0x0394 }, { 0x07c5, 0x0395 }, { 0x07c6, 0x0396 }, { 0x07c7, 0x0397 }, + { 0x07c8, 0x0398 }, { 0x07c9, 0x0399 }, { 0x07ca, 0x039a }, { 0x07cb, 0x039b }, + { 0x07cc, 0x039c }, { 0x07cd, 0x039d }, { 0x07ce, 0x039e }, { 0x07cf, 0x039f }, + { 0x07d0, 0x03a0 }, { 0x07d1, 0x03a1 }, { 0x07d2, 0x03a3 }, { 0x07d4, 0x03a4 }, + { 0x07d5, 0x03a5 }, { 0x07d6, 0x03a6 }, { 0x07d7, 0x03a7 }, { 0x07d8, 0x03a8 }, + { 0x07d9, 0x03a9 }, { 0x07e1, 0x03b1 }, { 0x07e2, 0x03b2 }, { 0x07e3, 0x03b3 }, + { 0x07e4, 0x03b4 }, { 0x07e5, 0x03b5 }, { 0x07e6, 0x03b6 }, { 0x07e7, 0x03b7 }, + { 0x07e8, 0x03b8 }, { 0x07e9, 0x03b9 }, { 0x07ea, 0x03ba }, { 0x07eb, 0x03bb }, + { 0x07ec, 0x03bc }, { 0x07ed, 0x03bd }, { 0x07ee, 0x03be }, { 0x07ef, 0x03bf }, + { 0x07f0, 0x03c0 }, { 0x07f1, 0x03c1 }, { 0x07f2, 0x03c3 }, { 0x07f3, 0x03c2 }, + { 0x07f4, 0x03c4 }, { 0x07f5, 0x03c5 }, { 0x07f6, 0x03c6 }, { 0x07f7, 0x03c7 }, + { 0x07f8, 0x03c8 }, { 0x07f9, 0x03c9 }, { 0x08a1, 0x23b7 }, { 0x08a2, 0x250c }, + { 0x08a3, 0x2500 }, { 0x08a4, 0x2320 }, { 0x08a5, 0x2321 }, { 0x08a6, 0x2502 }, + { 0x08a7, 0x23a1 }, { 0x08a8, 0x23a3 }, { 0x08a9, 0x23a4 }, { 0x08aa, 0x23a6 }, + { 0x08ab, 0x239b }, { 0x08ac, 0x239d }, { 0x08ad, 0x239e }, { 0x08ae, 0x23a0 }, + { 0x08af, 0x23a8 }, { 0x08b0, 0x23ac }, { 0x08bc, 0x2264 }, { 0x08bd, 0x2260 }, + { 0x08be, 0x2265 }, { 0x08bf, 0x222b }, { 0x08c0, 0x2234 }, { 0x08c1, 0x221d }, + { 0x08c2, 0x221e }, { 0x08c5, 0x2207 }, { 0x08c8, 0x223c }, { 0x08c9, 0x2243 }, + { 0x08cd, 0x21d4 }, { 0x08ce, 0x21d2 }, { 0x08cf, 0x2261 }, { 0x08d6, 0x221a }, + { 0x08da, 0x2282 }, { 0x08db, 0x2283 }, { 0x08dc, 0x2229 }, { 0x08dd, 0x222a }, + { 0x08de, 0x2227 }, { 0x08df, 0x2228 }, { 0x08ef, 0x2202 }, { 0x08f6, 0x0192 }, + { 0x08fb, 0x2190 }, { 0x08fc, 0x2191 }, { 0x08fd, 0x2192 }, { 0x08fe, 0x2193 }, + { 0x09e0, 0x25c6 }, { 0x09e1, 0x2592 }, { 0x09e2, 0x2409 }, { 0x09e3, 0x240c }, + { 0x09e4, 0x240d }, { 0x09e5, 0x240a }, { 0x09e8, 0x2424 }, { 0x09e9, 0x240b }, + { 0x09ea, 0x2518 }, { 0x09eb, 0x2510 }, { 0x09ec, 0x250c }, { 0x09ed, 0x2514 }, + { 0x09ee, 0x253c }, { 0x09ef, 0x23ba }, { 0x09f0, 0x23bb }, { 0x09f1, 0x2500 }, + { 0x09f2, 0x23bc }, { 0x09f3, 0x23bd }, { 0x09f4, 0x251c }, { 0x09f5, 0x2524 }, + { 0x09f6, 0x2534 }, { 0x09f7, 0x252c }, { 0x09f8, 0x2502 }, { 0x0aa1, 0x2003 }, + { 0x0aa2, 0x2002 }, { 0x0aa3, 0x2004 }, { 0x0aa4, 0x2005 }, { 0x0aa5, 0x2007 }, + { 0x0aa6, 0x2008 }, { 0x0aa7, 0x2009 }, { 0x0aa8, 0x200a }, { 0x0aa9, 0x2014 }, + { 0x0aaa, 0x2013 }, { 0x0aae, 0x2026 }, { 0x0aaf, 0x2025 }, { 0x0ab0, 0x2153 }, + { 0x0ab1, 0x2154 }, { 0x0ab2, 0x2155 }, { 0x0ab3, 0x2156 }, { 0x0ab4, 0x2157 }, + { 0x0ab5, 0x2158 }, { 0x0ab6, 0x2159 }, { 0x0ab7, 0x215a }, { 0x0ab8, 0x2105 }, + { 0x0abb, 0x2012 }, { 0x0abc, 0x2329 }, { 0x0abe, 0x232a }, { 0x0ac3, 0x215b }, + { 0x0ac4, 0x215c }, { 0x0ac5, 0x215d }, { 0x0ac6, 0x215e }, { 0x0ac9, 0x2122 }, + { 0x0aca, 0x2613 }, { 0x0acc, 0x25c1 }, { 0x0acd, 0x25b7 }, { 0x0ace, 0x25cb }, + { 0x0acf, 0x25af }, { 0x0ad0, 0x2018 }, { 0x0ad1, 0x2019 }, { 0x0ad2, 0x201c }, + { 0x0ad3, 0x201d }, { 0x0ad4, 0x211e }, { 0x0ad6, 0x2032 }, { 0x0ad7, 0x2033 }, + { 0x0ad9, 0x271d }, { 0x0adb, 0x25ac }, { 0x0adc, 0x25c0 }, { 0x0add, 0x25b6 }, + { 0x0ade, 0x25cf }, { 0x0adf, 0x25ae }, { 0x0ae0, 0x25e6 }, { 0x0ae1, 0x25ab }, + { 0x0ae2, 0x25ad }, { 0x0ae3, 0x25b3 }, { 0x0ae4, 0x25bd }, { 0x0ae5, 0x2606 }, + { 0x0ae6, 0x2022 }, { 0x0ae7, 0x25aa }, { 0x0ae8, 0x25b2 }, { 0x0ae9, 0x25bc }, + { 0x0aea, 0x261c }, { 0x0aeb, 0x261e }, { 0x0aec, 0x2663 }, { 0x0aed, 0x2666 }, + { 0x0aee, 0x2665 }, { 0x0af0, 0x2720 }, { 0x0af1, 0x2020 }, { 0x0af2, 0x2021 }, + { 0x0af3, 0x2713 }, { 0x0af4, 0x2717 }, { 0x0af5, 0x266f }, { 0x0af6, 0x266d }, + { 0x0af7, 0x2642 }, { 0x0af8, 0x2640 }, { 0x0af9, 0x260e }, { 0x0afa, 0x2315 }, + { 0x0afb, 0x2117 }, { 0x0afc, 0x2038 }, { 0x0afd, 0x201a }, { 0x0afe, 0x201e }, + { 0x0ba3, 0x003c }, { 0x0ba6, 0x003e }, { 0x0ba8, 0x2228 }, { 0x0ba9, 0x2227 }, + { 0x0bc0, 0x00af }, { 0x0bc2, 0x22a5 }, { 0x0bc3, 0x2229 }, { 0x0bc4, 0x230a }, + { 0x0bc6, 0x005f }, { 0x0bca, 0x2218 }, { 0x0bcc, 0x2395 }, { 0x0bce, 0x22a4 }, + { 0x0bcf, 0x25cb }, { 0x0bd3, 0x2308 }, { 0x0bd6, 0x222a }, { 0x0bd8, 0x2283 }, + { 0x0bda, 0x2282 }, { 0x0bdc, 0x22a2 }, { 0x0bfc, 0x22a3 }, { 0x0cdf, 0x2017 }, + { 0x0ce0, 0x05d0 }, { 0x0ce1, 0x05d1 }, { 0x0ce2, 0x05d2 }, { 0x0ce3, 0x05d3 }, + { 0x0ce4, 0x05d4 }, { 0x0ce5, 0x05d5 }, { 0x0ce6, 0x05d6 }, { 0x0ce7, 0x05d7 }, + { 0x0ce8, 0x05d8 }, { 0x0ce9, 0x05d9 }, { 0x0cea, 0x05da }, { 0x0ceb, 0x05db }, + { 0x0cec, 0x05dc }, { 0x0ced, 0x05dd }, { 0x0cee, 0x05de }, { 0x0cef, 0x05df }, + { 0x0cf0, 0x05e0 }, { 0x0cf1, 0x05e1 }, { 0x0cf2, 0x05e2 }, { 0x0cf3, 0x05e3 }, + { 0x0cf4, 0x05e4 }, { 0x0cf5, 0x05e5 }, { 0x0cf6, 0x05e6 }, { 0x0cf7, 0x05e7 }, + { 0x0cf8, 0x05e8 }, { 0x0cf9, 0x05e9 }, { 0x0cfa, 0x05ea }, { 0x0da1, 0x0e01 }, + { 0x0da2, 0x0e02 }, { 0x0da3, 0x0e03 }, { 0x0da4, 0x0e04 }, { 0x0da5, 0x0e05 }, + { 0x0da6, 0x0e06 }, { 0x0da7, 0x0e07 }, { 0x0da8, 0x0e08 }, { 0x0da9, 0x0e09 }, + { 0x0daa, 0x0e0a }, { 0x0dab, 0x0e0b }, { 0x0dac, 0x0e0c }, { 0x0dad, 0x0e0d }, + { 0x0dae, 0x0e0e }, { 0x0daf, 0x0e0f }, { 0x0db0, 0x0e10 }, { 0x0db1, 0x0e11 }, + { 0x0db2, 0x0e12 }, { 0x0db3, 0x0e13 }, { 0x0db4, 0x0e14 }, { 0x0db5, 0x0e15 }, + { 0x0db6, 0x0e16 }, { 0x0db7, 0x0e17 }, { 0x0db8, 0x0e18 }, { 0x0db9, 0x0e19 }, + { 0x0dba, 0x0e1a }, { 0x0dbb, 0x0e1b }, { 0x0dbc, 0x0e1c }, { 0x0dbd, 0x0e1d }, + { 0x0dbe, 0x0e1e }, { 0x0dbf, 0x0e1f }, { 0x0dc0, 0x0e20 }, { 0x0dc1, 0x0e21 }, + { 0x0dc2, 0x0e22 }, { 0x0dc3, 0x0e23 }, { 0x0dc4, 0x0e24 }, { 0x0dc5, 0x0e25 }, + { 0x0dc6, 0x0e26 }, { 0x0dc7, 0x0e27 }, { 0x0dc8, 0x0e28 }, { 0x0dc9, 0x0e29 }, + { 0x0dca, 0x0e2a }, { 0x0dcb, 0x0e2b }, { 0x0dcc, 0x0e2c }, { 0x0dcd, 0x0e2d }, + { 0x0dce, 0x0e2e }, { 0x0dcf, 0x0e2f }, { 0x0dd0, 0x0e30 }, { 0x0dd1, 0x0e31 }, + { 0x0dd2, 0x0e32 }, { 0x0dd3, 0x0e33 }, { 0x0dd4, 0x0e34 }, { 0x0dd5, 0x0e35 }, + { 0x0dd6, 0x0e36 }, { 0x0dd7, 0x0e37 }, { 0x0dd8, 0x0e38 }, { 0x0dd9, 0x0e39 }, + { 0x0dda, 0x0e3a }, { 0x0ddf, 0x0e3f }, { 0x0de0, 0x0e40 }, { 0x0de1, 0x0e41 }, + { 0x0de2, 0x0e42 }, { 0x0de3, 0x0e43 }, { 0x0de4, 0x0e44 }, { 0x0de5, 0x0e45 }, + { 0x0de6, 0x0e46 }, { 0x0de7, 0x0e47 }, { 0x0de8, 0x0e48 }, { 0x0de9, 0x0e49 }, + { 0x0dea, 0x0e4a }, { 0x0deb, 0x0e4b }, { 0x0dec, 0x0e4c }, { 0x0ded, 0x0e4d }, + { 0x0df0, 0x0e50 }, { 0x0df1, 0x0e51 }, { 0x0df2, 0x0e52 }, { 0x0df3, 0x0e53 }, + { 0x0df4, 0x0e54 }, { 0x0df5, 0x0e55 }, { 0x0df6, 0x0e56 }, { 0x0df7, 0x0e57 }, + { 0x0df8, 0x0e58 }, { 0x0df9, 0x0e59 }, { 0x0ea1, 0x3131 }, { 0x0ea2, 0x3132 }, + { 0x0ea3, 0x3133 }, { 0x0ea4, 0x3134 }, { 0x0ea5, 0x3135 }, { 0x0ea6, 0x3136 }, + { 0x0ea7, 0x3137 }, { 0x0ea8, 0x3138 }, { 0x0ea9, 0x3139 }, { 0x0eaa, 0x313a }, + { 0x0eab, 0x313b }, { 0x0eac, 0x313c }, { 0x0ead, 0x313d }, { 0x0eae, 0x313e }, + { 0x0eaf, 0x313f }, { 0x0eb0, 0x3140 }, { 0x0eb1, 0x3141 }, { 0x0eb2, 0x3142 }, + { 0x0eb3, 0x3143 }, { 0x0eb4, 0x3144 }, { 0x0eb5, 0x3145 }, { 0x0eb6, 0x3146 }, + { 0x0eb7, 0x3147 }, { 0x0eb8, 0x3148 }, { 0x0eb9, 0x3149 }, { 0x0eba, 0x314a }, + { 0x0ebb, 0x314b }, { 0x0ebc, 0x314c }, { 0x0ebd, 0x314d }, { 0x0ebe, 0x314e }, + { 0x0ebf, 0x314f }, { 0x0ec0, 0x3150 }, { 0x0ec1, 0x3151 }, { 0x0ec2, 0x3152 }, + { 0x0ec3, 0x3153 }, { 0x0ec4, 0x3154 }, { 0x0ec5, 0x3155 }, { 0x0ec6, 0x3156 }, + { 0x0ec7, 0x3157 }, { 0x0ec8, 0x3158 }, { 0x0ec9, 0x3159 }, { 0x0eca, 0x315a }, + { 0x0ecb, 0x315b }, { 0x0ecc, 0x315c }, { 0x0ecd, 0x315d }, { 0x0ece, 0x315e }, + { 0x0ecf, 0x315f }, { 0x0ed0, 0x3160 }, { 0x0ed1, 0x3161 }, { 0x0ed2, 0x3162 }, + { 0x0ed3, 0x3163 }, { 0x0ed4, 0x11a8 }, { 0x0ed5, 0x11a9 }, { 0x0ed6, 0x11aa }, + { 0x0ed7, 0x11ab }, { 0x0ed8, 0x11ac }, { 0x0ed9, 0x11ad }, { 0x0eda, 0x11ae }, + { 0x0edb, 0x11af }, { 0x0edc, 0x11b0 }, { 0x0edd, 0x11b1 }, { 0x0ede, 0x11b2 }, + { 0x0edf, 0x11b3 }, { 0x0ee0, 0x11b4 }, { 0x0ee1, 0x11b5 }, { 0x0ee2, 0x11b6 }, + { 0x0ee3, 0x11b7 }, { 0x0ee4, 0x11b8 }, { 0x0ee5, 0x11b9 }, { 0x0ee6, 0x11ba }, + { 0x0ee7, 0x11bb }, { 0x0ee8, 0x11bc }, { 0x0ee9, 0x11bd }, { 0x0eea, 0x11be }, + { 0x0eeb, 0x11bf }, { 0x0eec, 0x11c0 }, { 0x0eed, 0x11c1 }, { 0x0eee, 0x11c2 }, + { 0x0eef, 0x316d }, { 0x0ef0, 0x3171 }, { 0x0ef1, 0x3178 }, { 0x0ef2, 0x317f }, + { 0x0ef3, 0x3181 }, { 0x0ef4, 0x3184 }, { 0x0ef5, 0x3186 }, { 0x0ef6, 0x318d }, + { 0x0ef7, 0x318e }, { 0x0ef8, 0x11eb }, { 0x0ef9, 0x11f0 }, { 0x0efa, 0x11f9 }, + { 0x0eff, 0x20a9 }, { 0x13a4, 0x20ac }, { 0x13bc, 0x0152 }, { 0x13bd, 0x0153 }, + { 0x13be, 0x0178 }, { 0x20ac, 0x20ac }, +}; + +static unsigned int ImGui_ImplQNX_KeySymToUnicode(unsigned int keysym) +{ + // Latin-1 keysyms map directly to Unicode. + if ((keysym >= 0x0020u && keysym <= 0x007eu) || + (keysym >= 0x00a0u && keysym <= 0x00ffu)) + { + return keysym; + } + + // X11-style directly encoded UCS keysyms use 0x01000000 | codepoint. + if ((keysym & 0xff000000u) == 0x01000000u) + { + const unsigned int codepoint = keysym & 0x00ffffffu; + return codepoint <= IM_UNICODE_CODEPOINT_MAX ? codepoint : 0u; + } + + int min = 0; + int max = (int)(sizeof(ImGui_ImplQNX_KeySymTable) / sizeof(ImGui_ImplQNX_KeySymTable[0])) - 1; + while (max >= min) + { + const int mid = min + (max - min) / 2; + if (ImGui_ImplQNX_KeySymTable[mid].KeySym < keysym) + { + min = mid + 1; + } + else if (ImGui_ImplQNX_KeySymTable[mid].KeySym > keysym) + { + max = mid - 1; + } + else + { + return ImGui_ImplQNX_KeySymTable[mid].Unicode; + } + } + + return 0u; +} + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_qnx.h b/backends/imgui_impl_qnx.h new file mode 100644 index 000000000..6436dfea9 --- /dev/null +++ b/backends/imgui_impl_qnx.h @@ -0,0 +1,36 @@ +// dear imgui: Platform Backend for QNX Screen +// This needs to be used along with a Renderer Backend (e.g. OpenGL3, Vulkan). + +// Implemented features: +// [X] Platform: Keyboard support. Uses io.AddKeyEvent() and native QNX key symbols. +// [X] Platform: Mouse support. Handles position, three buttons and horizontal/vertical wheels. +// [X] Platform: Touch support. Maps the first active touch contact to the primary mouse button. +// [X] Platform: Focus and window-size updates. +// Missing features or Issues: +// [ ] Platform: Clipboard support. +// [ ] Platform: Gamepad support. +// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). +// [ ] Platform: Multi-viewport support. + +// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. +// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API +#ifndef IMGUI_DISABLE + +#include + +// The application owns the Screen context, window and event object. +// Poll events with screen_get_event() and pass each non-NONE event to ProcessEvent(). +IMGUI_IMPL_API bool ImGui_ImplQNX_Init(screen_context_t context, screen_window_t window); +IMGUI_IMPL_API void ImGui_ImplQNX_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplQNX_NewFrame(); +IMGUI_IMPL_API bool ImGui_ImplQNX_ProcessEvent(screen_event_t event); + +#endif // #ifndef IMGUI_DISABLE diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index f72391f3e..bab76fb60 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -79,6 +79,7 @@ List of Platforms Backends: imgui_impl_osx.mm ; macOS native API (not as feature complete as glfw/sdl backends) imgui_impl_sdl2.cpp ; SDL2 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org imgui_impl_sdl3.cpp ; SDL3 (Windows, macOS, Linux, iOS, Android) https://www.libsdl.org + imgui_impl_qnx.cpp ; QNX Screen imgui_impl_win32.cpp ; Win32 native API (Windows) imgui_impl_glut.cpp ; GLUT/FreeGLUT (this is prehistoric software and absolutely not recommended today!) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 93dc4c9f7..3197f28e6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -56,6 +56,10 @@ Other Changes: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. - Circle and Curves tessellation error are scaled accordingly. +- Backends: + - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] +- Examples: + - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] ----------------------------------------------------------------------- diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 6315a3a5c..89e9bd0df 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -117,6 +117,16 @@ Because this application doesn't create a window nor a graphic context, there's Please note that imgui_impl_null itself is a rather empty backend. We provide it for consistency but it is similarly easy to create a skeleton application without the null backend. +[example_qnx_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_qnx_opengl3/)
+QNX Screen + OpenGL ES 3 example.
+= main.cpp + imgui_impl_qnx.cpp + imgui_impl_opengl3.cpp
+Uses the QNX recursive make build system and compiles the OpenGL3 renderer with `IMGUI_IMPL_OPENGL_ES3`. + +[example_qnx_vulkan/](https://github.com/ocornut/imgui/blob/master/examples/example_qnx_vulkan/)
+QNX Screen + Vulkan example using `VK_QNX_screen_surface`.
+= main.cpp + imgui_impl_qnx.cpp + imgui_impl_vulkan.cpp
+Uses the QNX recursive make build system and the Vulkan backend's example helper structures. + [example_sdl2_directx11/](https://github.com/ocornut/imgui/blob/master/examples/example_sdl2_directx11/)
SDL2 + DirectX11 example, Windows only.
= main.cpp + imgui_impl_sdl2.cpp + imgui_impl_dx11.cpp
diff --git a/examples/example_qnx_opengl3/.gitignore b/examples/example_qnx_opengl3/.gitignore new file mode 100644 index 000000000..12b50871c --- /dev/null +++ b/examples/example_qnx_opengl3/.gitignore @@ -0,0 +1,19 @@ +# ignore all files +* + +# unignore all dirs +!/**/ + +# unignore all with extensions +!*.* + +# unignore makefiles +!Makefile + +# re-ignore qnx build artefacts +*.o +*.so* +*.a +*.pinfo +*.dep +*.gcno diff --git a/examples/example_qnx_opengl3/Makefile b/examples/example_qnx_opengl3/Makefile new file mode 100644 index 000000000..3beecfb7e --- /dev/null +++ b/examples/example_qnx_opengl3/Makefile @@ -0,0 +1,2 @@ +LIST=OS +include recurse.mk diff --git a/examples/example_qnx_opengl3/README.md b/examples/example_qnx_opengl3/README.md new file mode 100644 index 000000000..2fd97d6d3 --- /dev/null +++ b/examples/example_qnx_opengl3/README.md @@ -0,0 +1,29 @@ +# Dear ImGui QNX Screen + OpenGL ES 3 example + +This example uses: + +- `imgui_impl_qnx.cpp` as the QNX Screen platform backend +- `imgui_impl_opengl3.cpp` as the renderer backend +- EGL to create an OpenGL ES 3 context and Screen window surface + +`imgui_impl_opengl3.cpp` supports desktop OpenGL and OpenGL ES. On QNX this +example compiles it with `IMGUI_IMPL_OPENGL_ES3` and uses `#version 300 es`. +No separate Screen/OpenGL renderer backend is required. + +## Build + +Load the QNX SDP environment, then run: + +```sh +cd examples/example_qnx_opengl3 +make +``` + +The QNX recursive make files build for the CPUs enabled in your SDP +configuration and install the program as `example_qnx_opengl3`. + +## Input + +The application polls its `screen_context_t` event queue and forwards every +non-`SCREEN_EVENT_NONE` event to `ImGui_ImplQNX_ProcessEvent()`. +Escape exits the example. diff --git a/examples/example_qnx_opengl3/common.mk b/examples/example_qnx_opengl3/common.mk new file mode 100644 index 000000000..d162d684e --- /dev/null +++ b/examples/example_qnx_opengl3/common.mk @@ -0,0 +1,31 @@ +ifndef QCONFIG +QCONFIG=qconfig.mk +endif +include $(QCONFIG) + +NAME=example_qnx_opengl3 + +SRCS = \ + main.cpp \ + imgui.cpp \ + imgui_demo.cpp \ + imgui_draw.cpp \ + imgui_tables.cpp \ + imgui_widgets.cpp \ + imgui_impl_qnx.cpp \ + imgui_impl_opengl3.cpp + +EXTRA_SRCVPATH += $(PROJECT_ROOT)/../.. $(PROJECT_ROOT)/../../backends +EXTRA_INCVPATH += $(PROJECT_ROOT)/../.. $(PROJECT_ROOT)/../../backends + +CXXFLAGS += -std=gnu++11 -DIMGUI_IMPL_OPENGL_ES3 +LIBS += EGL GLESv2 screen m + +INSTALLDIR=$(firstword $(INSTALLDIR_$(OS)) usr/bin) + +define PINFO +PINFO DESCRIPTION=Dear ImGui QNX Screen and OpenGL ES 3 example +endef + +include $(MKFILES_ROOT)/qmacros.mk +include $(MKFILES_ROOT)/qtargets.mk diff --git a/examples/example_qnx_opengl3/example_qnx_opengl3.use b/examples/example_qnx_opengl3/example_qnx_opengl3.use new file mode 100644 index 000000000..6361d35cc --- /dev/null +++ b/examples/example_qnx_opengl3/example_qnx_opengl3.use @@ -0,0 +1,6 @@ +Dear ImGui QNX Screen + OpenGL ES 3 example + +Usage: example_qnx_opengl3 + +The example uses the fullscreen size reported by QNX Screen, displays the Dear ImGui demo UI, +and exits when Escape is pressed or the Screen window receives a close event. diff --git a/examples/example_qnx_opengl3/main.cpp b/examples/example_qnx_opengl3/main.cpp new file mode 100644 index 000000000..76cc3a866 --- /dev/null +++ b/examples/example_qnx_opengl3/main.cpp @@ -0,0 +1,299 @@ +// Dear ImGui: standalone example application for QNX Screen + OpenGL ES 3 + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#include "imgui.h" +#include "imgui_impl_qnx.h" +#include "imgui_impl_opengl3.h" +#include // printf, fprintf +#include +#include +#include +#include +#include +#include + +#ifndef EGL_OPENGL_ES3_BIT +#ifdef EGL_OPENGL_ES3_BIT_KHR +#define EGL_OPENGL_ES3_BIT EGL_OPENGL_ES3_BIT_KHR +#else +#define EGL_OPENGL_ES3_BIT 0x0040 +#endif +#endif + +struct QNXWindow +{ + screen_context_t Context = nullptr; + screen_window_t Window = nullptr; + screen_event_t Event = nullptr; + EGLDisplay Display = EGL_NO_DISPLAY; + EGLContext GLContext = EGL_NO_CONTEXT; + EGLSurface GLSurface = EGL_NO_SURFACE; + int Size[2] = {}; +}; + +static bool CreateQNXWindow(QNXWindow* window) +{ + if (screen_create_context(&window->Context, 0) != 0 || + screen_create_window(&window->Window, window->Context) != 0 || + screen_create_event(&window->Event) != 0) + { + fprintf(stderr, "QNX Screen initialization failed: %s\n", strerror(errno)); + return false; + } + + // A newly created QNX Screen window reports the fullscreen dimensions. + if (screen_get_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || window->Size[0] <= 0 || window->Size[1] <= 0) + { + fprintf(stderr, "Failed to obtain QNX Screen window size: %s\n", strerror(errno)); + return false; + } + + const int usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_OPENGL_ES3 | SCREEN_USAGE_NATIVE; + const int transparency = SCREEN_TRANSPARENCY_NONE; + const int swap_interval = 1; + if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_USAGE, &usage) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_TRANSPARENCY, &transparency) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SWAP_INTERVAL, &swap_interval) != 0) + { + fprintf(stderr, "Failed to configure QNX Screen window: %s\n", strerror(errno)); + return false; + } + + window->Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (window->Display == EGL_NO_DISPLAY || eglInitialize(window->Display, nullptr, nullptr) != EGL_TRUE || eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) + { + fprintf(stderr, "EGL initialization failed: 0x%04x\n", eglGetError()); + return false; + } + + const EGLint config_attributes[] = + { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_NONE + }; + EGLConfig config = nullptr; + EGLint config_count = 0; + if (eglChooseConfig(window->Display, config_attributes, &config, 1, &config_count) != EGL_TRUE || config_count == 0) + { + fprintf(stderr, "eglChooseConfig() failed: 0x%04x\n", eglGetError()); + return false; + } + + EGLint native_visual = 0; + if (eglGetConfigAttrib(window->Display, config, EGL_NATIVE_VISUAL_ID, &native_visual) != EGL_TRUE || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_FORMAT, &native_visual) != 0 || + screen_create_window_buffers(window->Window, 2) != 0) + { + fprintf(stderr, "Failed to create QNX Screen buffers: %s\n", strerror(errno)); + return false; + } + + const EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + window->GLContext = eglCreateContext(window->Display, config, EGL_NO_CONTEXT, context_attributes); + window->GLSurface = eglCreateWindowSurface(window->Display, config, window->Window, nullptr); + if (window->GLContext == EGL_NO_CONTEXT || window->GLSurface == EGL_NO_SURFACE || + eglMakeCurrent(window->Display, window->GLSurface, window->GLSurface, window->GLContext) != EGL_TRUE || + eglSwapInterval(window->Display, 1) != EGL_TRUE) + { + fprintf(stderr, "EGL context creation failed: 0x%04x\n", eglGetError()); + return false; + } + + const int visible = 1; + if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_VISIBLE, &visible) != 0) + { + fprintf(stderr, "Failed to show QNX Screen window: %s\n", strerror(errno)); + return false; + } + return true; +} + +static void DestroyQNXWindow(QNXWindow* window) +{ + if (window->Display != EGL_NO_DISPLAY) + { + eglMakeCurrent(window->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (window->GLSurface != EGL_NO_SURFACE) + eglDestroySurface(window->Display, window->GLSurface); + if (window->GLContext != EGL_NO_CONTEXT) + eglDestroyContext(window->Display, window->GLContext); + eglTerminate(window->Display); + } + if (window->Event != nullptr) + screen_destroy_event(window->Event); + if (window->Window != nullptr) + screen_destroy_window(window->Window); + if (window->Context != nullptr) + screen_destroy_context(window->Context); +} + +static bool ProcessQNXEvents(QNXWindow* window) +{ + for (;;) + { + if (screen_get_event(window->Context, window->Event, 0) != 0) + return false; + + int type = SCREEN_EVENT_NONE; + if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_TYPE, &type) != 0) + return false; + if (type == SCREEN_EVENT_NONE) + return true; + + ImGui_ImplQNX_ProcessEvent(window->Event); + if (type == SCREEN_EVENT_CLOSE) + return false; + if (type == SCREEN_EVENT_KEYBOARD) + { + int flags = 0; + int key_cap = 0; + if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_FLAGS, &flags) == 0 && + screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_KEY_CAP, &key_cap) == 0 && + (flags & SCREEN_FLAG_KEY_DOWN) != 0 && key_cap == KEYCODE_ESCAPE) + return false; + } + } +} + +// Main code +int main(int, char**) +{ + // Setup QNX Screen and EGL + QNXWindow window; + if (!CreateQNXWindow(&window)) + { + DestroyQNXWindow(&window); + return 1; + } + + // GL ES 3.0 + GLSL 300 es + const char* glsl_version = "#version 300 es"; + float main_scale = 1.0f; + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) + + // Setup Platform/Renderer backends + ImGui_ImplQNX_Init(window.Context, window.Window); + ImGui_ImplOpenGL3_Init(glsl_version); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Main loop + bool done = false; + while (!done) + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + if (!ProcessQNXEvents(&window)) + done = true; + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplQNX_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + eglSwapBuffers(window.Display, window.GLSurface); + } + + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplQNX_Shutdown(); + ImGui::DestroyContext(); + + DestroyQNXWindow(&window); + + return 0; +} diff --git a/examples/example_qnx_opengl3/nto/Makefile b/examples/example_qnx_opengl3/nto/Makefile new file mode 100644 index 000000000..0cc5eae2d --- /dev/null +++ b/examples/example_qnx_opengl3/nto/Makefile @@ -0,0 +1,8 @@ +LIST=CPU +ifndef QRECURSE +QRECURSE=recurse.mk +ifdef QCONFIG +QRDIR=$(dir $(QCONFIG)) +endif +endif +include $(QRDIR)$(QRECURSE) diff --git a/examples/example_qnx_opengl3/nto/aarch64/Makefile b/examples/example_qnx_opengl3/nto/aarch64/Makefile new file mode 100644 index 000000000..0e22650c0 --- /dev/null +++ b/examples/example_qnx_opengl3/nto/aarch64/Makefile @@ -0,0 +1,8 @@ +LIST=VARIANT +ifndef QRECURSE +QRECURSE=recurse.mk +ifdef QCONFIG +QRDIR=$(dir $(QCONFIG)) +endif +endif +include $(QRDIR)$(QRECURSE) diff --git a/examples/example_qnx_opengl3/nto/aarch64/o.le/Makefile b/examples/example_qnx_opengl3/nto/aarch64/o.le/Makefile new file mode 100644 index 000000000..0f9d7b9a9 --- /dev/null +++ b/examples/example_qnx_opengl3/nto/aarch64/o.le/Makefile @@ -0,0 +1 @@ +include ../../../common.mk diff --git a/examples/example_qnx_opengl3/nto/x86_64/Makefile b/examples/example_qnx_opengl3/nto/x86_64/Makefile new file mode 100644 index 000000000..0e22650c0 --- /dev/null +++ b/examples/example_qnx_opengl3/nto/x86_64/Makefile @@ -0,0 +1,8 @@ +LIST=VARIANT +ifndef QRECURSE +QRECURSE=recurse.mk +ifdef QCONFIG +QRDIR=$(dir $(QCONFIG)) +endif +endif +include $(QRDIR)$(QRECURSE) diff --git a/examples/example_qnx_opengl3/nto/x86_64/o/Makefile b/examples/example_qnx_opengl3/nto/x86_64/o/Makefile new file mode 100644 index 000000000..0f9d7b9a9 --- /dev/null +++ b/examples/example_qnx_opengl3/nto/x86_64/o/Makefile @@ -0,0 +1 @@ +include ../../../common.mk diff --git a/examples/example_qnx_vulkan/.gitignore b/examples/example_qnx_vulkan/.gitignore new file mode 100644 index 000000000..12b50871c --- /dev/null +++ b/examples/example_qnx_vulkan/.gitignore @@ -0,0 +1,19 @@ +# ignore all files +* + +# unignore all dirs +!/**/ + +# unignore all with extensions +!*.* + +# unignore makefiles +!Makefile + +# re-ignore qnx build artefacts +*.o +*.so* +*.a +*.pinfo +*.dep +*.gcno diff --git a/examples/example_qnx_vulkan/Makefile b/examples/example_qnx_vulkan/Makefile new file mode 100644 index 000000000..3beecfb7e --- /dev/null +++ b/examples/example_qnx_vulkan/Makefile @@ -0,0 +1,2 @@ +LIST=OS +include recurse.mk diff --git a/examples/example_qnx_vulkan/README.md b/examples/example_qnx_vulkan/README.md new file mode 100644 index 000000000..13eba6f6f --- /dev/null +++ b/examples/example_qnx_vulkan/README.md @@ -0,0 +1,29 @@ +# Dear ImGui QNX Screen + Vulkan example + +This example uses: + +- `imgui_impl_qnx.cpp` as the QNX Screen platform backend +- the unmodified `imgui_impl_vulkan.cpp` renderer backend +- `vkCreateScreenSurfaceQNX()` and `VK_QNX_screen_surface` + +No separate Screen/Vulkan renderer backend is required. The QNX-specific work +is limited to the Screen platform backend, native window setup, and Vulkan WSI +surface creation. + +## Build + +Load the QNX SDP environment, then run: + +```sh +cd examples/example_qnx_vulkan +make +``` + +The build defines `VK_USE_PLATFORM_SCREEN_QNX` and links `libvulkan` and +`libscreen`. + +## Input and resize + +The application forwards Screen input events to `ImGui_ImplQNX_ProcessEvent()`. +It recreates the Vulkan swapchain when the Screen window size changes or Vulkan +reports `VK_ERROR_OUT_OF_DATE_KHR` / `VK_SUBOPTIMAL_KHR`. Escape exits. diff --git a/examples/example_qnx_vulkan/common.mk b/examples/example_qnx_vulkan/common.mk new file mode 100644 index 000000000..7d99539aa --- /dev/null +++ b/examples/example_qnx_vulkan/common.mk @@ -0,0 +1,31 @@ +ifndef QCONFIG +QCONFIG=qconfig.mk +endif +include $(QCONFIG) + +NAME=example_qnx_vulkan + +SRCS = \ + main.cpp \ + imgui.cpp \ + imgui_demo.cpp \ + imgui_draw.cpp \ + imgui_tables.cpp \ + imgui_widgets.cpp \ + imgui_impl_qnx.cpp \ + imgui_impl_vulkan.cpp + +EXTRA_SRCVPATH += $(PROJECT_ROOT)/../.. $(PROJECT_ROOT)/../../backends +EXTRA_INCVPATH += $(PROJECT_ROOT)/../.. $(PROJECT_ROOT)/../../backends + +CXXFLAGS += -std=gnu++11 -DVK_USE_PLATFORM_SCREEN_QNX=1 +LIBS += vulkan screen m + +INSTALLDIR=$(firstword $(INSTALLDIR_$(OS)) usr/bin) + +define PINFO +PINFO DESCRIPTION=Dear ImGui QNX Screen and Vulkan example +endef + +include $(MKFILES_ROOT)/qmacros.mk +include $(MKFILES_ROOT)/qtargets.mk diff --git a/examples/example_qnx_vulkan/example_qnx_vulkan.use b/examples/example_qnx_vulkan/example_qnx_vulkan.use new file mode 100644 index 000000000..893eb5c41 --- /dev/null +++ b/examples/example_qnx_vulkan/example_qnx_vulkan.use @@ -0,0 +1,7 @@ +Dear ImGui QNX Screen + Vulkan example + +Usage: example_qnx_vulkan + +The example uses the fullscreen size reported by QNX Screen, creates a VK_QNX_screen_surface +swapchain, displays the Dear ImGui demo UI, and exits when Escape is pressed +or the Screen window receives a close event. diff --git a/examples/example_qnx_vulkan/main.cpp b/examples/example_qnx_vulkan/main.cpp new file mode 100644 index 000000000..ae309ad15 --- /dev/null +++ b/examples/example_qnx_vulkan/main.cpp @@ -0,0 +1,626 @@ +// Dear ImGui: standalone example application for QNX Screen + Vulkan + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. +// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. +// You will use those if you want to use this rendering backend in your engine/app. +// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by +// the backend itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. +// Read comments in imgui_impl_vulkan.h. + +#ifndef VK_USE_PLATFORM_SCREEN_QNX +#define VK_USE_PLATFORM_SCREEN_QNX +#endif +#include "imgui.h" +#include "imgui_impl_qnx.h" +#include "imgui_impl_vulkan.h" +#include // printf, fprintf +#include // abort +#include +#include +#include +#include +#include + +// Volk headers +#ifdef IMGUI_IMPL_VULKAN_USE_VOLK +#define VOLK_IMPLEMENTATION +#include +#endif + +//#define APP_USE_UNLIMITED_FRAME_RATE +#ifdef _DEBUG +#define APP_USE_VULKAN_DEBUG_REPORT +static VkDebugReportCallbackEXT g_DebugReport = VK_NULL_HANDLE; +#endif + +// Data +static VkAllocationCallbacks* g_Allocator = nullptr; +static VkInstance g_Instance = VK_NULL_HANDLE; +static VkPhysicalDevice g_PhysicalDevice = VK_NULL_HANDLE; +static VkDevice g_Device = VK_NULL_HANDLE; +static uint32_t g_QueueFamily = (uint32_t)-1; +static VkQueue g_Queue = VK_NULL_HANDLE; +static VkPipelineCache g_PipelineCache = VK_NULL_HANDLE; +static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; + +static ImGui_ImplVulkanH_Window g_MainWindowData; +static uint32_t g_MinImageCount = 2; +static bool g_SwapChainRebuild = false; + +static void check_vk_result(VkResult err) +{ + if (err == VK_SUCCESS) + return; + fprintf(stderr, "[vulkan] Error: VkResult = %d\n", err); + if (err < 0) + abort(); +} + +#ifdef APP_USE_VULKAN_DEBUG_REPORT +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) +{ + (void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments + fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage); + return VK_FALSE; +} +#endif // APP_USE_VULKAN_DEBUG_REPORT + +static bool IsExtensionAvailable(const ImVector& properties, const char* extension) +{ + for (const VkExtensionProperties& p : properties) + if (strcmp(p.extensionName, extension) == 0) + return true; + return false; +} + +static void SetupVulkan(ImVector instance_extensions) +{ + VkResult err; +#ifdef IMGUI_IMPL_VULKAN_USE_VOLK + volkInitialize(); +#endif + + // Create Vulkan Instance + { + VkInstanceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + + // Enumerate available extensions + uint32_t properties_count; + ImVector properties; + vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, nullptr); + properties.resize(properties_count); + err = vkEnumerateInstanceExtensionProperties(nullptr, &properties_count, properties.Data); + check_vk_result(err); + + // Enable required extensions + if (IsExtensionAvailable(properties, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) + instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); +#ifdef VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME + if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) + { + instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + } +#endif + + // Enabling validation layers +#ifdef APP_USE_VULKAN_DEBUG_REPORT + const char* layers[] = { "VK_LAYER_KHRONOS_validation" }; + create_info.enabledLayerCount = 1; + create_info.ppEnabledLayerNames = layers; + instance_extensions.push_back("VK_EXT_debug_report"); +#endif + + // Create Vulkan Instance + create_info.enabledExtensionCount = (uint32_t)instance_extensions.Size; + create_info.ppEnabledExtensionNames = instance_extensions.Data; + err = vkCreateInstance(&create_info, g_Allocator, &g_Instance); + check_vk_result(err); +#ifdef IMGUI_IMPL_VULKAN_USE_VOLK + volkLoadInstance(g_Instance); +#endif + + // Setup the debug report callback +#ifdef APP_USE_VULKAN_DEBUG_REPORT + auto f_vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkCreateDebugReportCallbackEXT"); + IM_ASSERT(f_vkCreateDebugReportCallbackEXT != nullptr); + VkDebugReportCallbackCreateInfoEXT debug_report_ci = {}; + debug_report_ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + debug_report_ci.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; + debug_report_ci.pfnCallback = debug_report; + debug_report_ci.pUserData = nullptr; + err = f_vkCreateDebugReportCallbackEXT(g_Instance, &debug_report_ci, g_Allocator, &g_DebugReport); + check_vk_result(err); +#endif + } + + // Select Physical Device (GPU) + g_PhysicalDevice = ImGui_ImplVulkanH_SelectPhysicalDevice(g_Instance); + IM_ASSERT(g_PhysicalDevice != VK_NULL_HANDLE); + + // Select graphics queue family + g_QueueFamily = ImGui_ImplVulkanH_SelectQueueFamilyIndex(g_PhysicalDevice); + IM_ASSERT(g_QueueFamily != (uint32_t)-1); + + // Create Logical Device (with 1 queue) + { + ImVector device_extensions; + device_extensions.push_back("VK_KHR_swapchain"); + + // Enumerate physical device extension + uint32_t properties_count; + ImVector properties; + vkEnumerateDeviceExtensionProperties(g_PhysicalDevice, nullptr, &properties_count, nullptr); + properties.resize(properties_count); + vkEnumerateDeviceExtensionProperties(g_PhysicalDevice, nullptr, &properties_count, properties.Data); +#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME + if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) + device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); +#endif + + const float queue_priority[] = { 1.0f }; + VkDeviceQueueCreateInfo queue_info[1] = {}; + queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info[0].queueFamilyIndex = g_QueueFamily; + queue_info[0].queueCount = 1; + queue_info[0].pQueuePriorities = queue_priority; + VkDeviceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_info.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); + create_info.pQueueCreateInfos = queue_info; + create_info.enabledExtensionCount = (uint32_t)device_extensions.Size; + create_info.ppEnabledExtensionNames = device_extensions.Data; + err = vkCreateDevice(g_PhysicalDevice, &create_info, g_Allocator, &g_Device); + check_vk_result(err); + vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue); + } + + // Create Descriptor Pool + // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. + { + VkDescriptorPoolSize pool_sizes[] = + { + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE }, + { VK_DESCRIPTOR_TYPE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE }, + }; + VkDescriptorPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + pool_info.maxSets = 0; + for (VkDescriptorPoolSize& pool_size : pool_sizes) + pool_info.maxSets += pool_size.descriptorCount; + pool_info.poolSizeCount = (uint32_t)IM_COUNTOF(pool_sizes); + pool_info.pPoolSizes = pool_sizes; + err = vkCreateDescriptorPool(g_Device, &pool_info, g_Allocator, &g_DescriptorPool); + check_vk_result(err); + } +} + +// All the ImGui_ImplVulkanH_XXX structures/functions are optional helpers used by the demo. +// Your real engine/app may not use them. +static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface, int width, int height) +{ + // Check for WSI support + VkBool32 res; + vkGetPhysicalDeviceSurfaceSupportKHR(g_PhysicalDevice, g_QueueFamily, surface, &res); + if (res != VK_TRUE) + { + fprintf(stderr, "Error no WSI support on physical device 0\n"); + exit(-1); + } + + // Select Surface Format + const VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }; + const VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + wd->Surface = surface; + wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(g_PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_COUNTOF(requestSurfaceImageFormat), requestSurfaceColorSpace); + + // Select Present Mode +#ifdef APP_USE_UNLIMITED_FRAME_RATE + VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR }; +#else + VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_FIFO_KHR }; +#endif + wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(g_PhysicalDevice, wd->Surface, &present_modes[0], IM_COUNTOF(present_modes)); + //printf("[vulkan] Selected PresentMode = %d\n", wd->PresentMode); + + // Create SwapChain, RenderPass, Framebuffer, etc. + IM_ASSERT(g_MinImageCount >= 2); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, width, height, g_MinImageCount, 0); +} + +static void CleanupVulkan() +{ + vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); + +#ifdef APP_USE_VULKAN_DEBUG_REPORT + // Remove the debug report callback + auto f_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(g_Instance, "vkDestroyDebugReportCallbackEXT"); + f_vkDestroyDebugReportCallbackEXT(g_Instance, g_DebugReport, g_Allocator); +#endif // APP_USE_VULKAN_DEBUG_REPORT + + vkDestroyDevice(g_Device, g_Allocator); + vkDestroyInstance(g_Instance, g_Allocator); +} + +static void CleanupVulkanWindow(ImGui_ImplVulkanH_Window* wd) +{ + ImGui_ImplVulkanH_DestroyWindow(g_Instance, g_Device, wd, g_Allocator); + vkDestroySurfaceKHR(g_Instance, wd->Surface, g_Allocator); +} + +static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) +{ + VkSemaphore image_acquired_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore; + VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; + VkResult err = vkAcquireNextImageKHR(g_Device, wd->Swapchain, UINT64_MAX, image_acquired_semaphore, VK_NULL_HANDLE, &wd->FrameIndex); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) + g_SwapChainRebuild = true; + if (err == VK_ERROR_OUT_OF_DATE_KHR) + return; + if (err != VK_SUBOPTIMAL_KHR) + check_vk_result(err); + + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex]; + { + err = vkWaitForFences(g_Device, 1, &fd->Fence, VK_TRUE, UINT64_MAX); // wait indefinitely instead of periodically checking + check_vk_result(err); + + err = vkResetFences(g_Device, 1, &fd->Fence); + check_vk_result(err); + } + { + err = vkResetCommandPool(g_Device, fd->CommandPool, 0); + check_vk_result(err); + VkCommandBufferBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + err = vkBeginCommandBuffer(fd->CommandBuffer, &info); + check_vk_result(err); + } + { + VkRenderPassBeginInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + info.renderPass = wd->RenderPass; + info.framebuffer = fd->Framebuffer; + info.renderArea.extent.width = wd->Width; + info.renderArea.extent.height = wd->Height; + info.clearValueCount = 1; + info.pClearValues = &wd->ClearValue; + vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); + } + + // Record dear imgui primitives into command buffer + ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer); + + // Submit command buffer + vkCmdEndRenderPass(fd->CommandBuffer); + { + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &image_acquired_semaphore; + info.pWaitDstStageMask = &wait_stage; + info.commandBufferCount = 1; + info.pCommandBuffers = &fd->CommandBuffer; + info.signalSemaphoreCount = 1; + info.pSignalSemaphores = &render_complete_semaphore; + + err = vkEndCommandBuffer(fd->CommandBuffer); + check_vk_result(err); + err = vkQueueSubmit(g_Queue, 1, &info, fd->Fence); + check_vk_result(err); + } +} + +static void FramePresent(ImGui_ImplVulkanH_Window* wd) +{ + if (g_SwapChainRebuild) + return; + VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore; + VkPresentInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + info.waitSemaphoreCount = 1; + info.pWaitSemaphores = &render_complete_semaphore; + info.swapchainCount = 1; + info.pSwapchains = &wd->Swapchain; + info.pImageIndices = &wd->FrameIndex; + VkResult err = vkQueuePresentKHR(g_Queue, &info); + if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) + g_SwapChainRebuild = true; + if (err == VK_ERROR_OUT_OF_DATE_KHR) + return; + if (err != VK_SUBOPTIMAL_KHR) + check_vk_result(err); + wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->SemaphoreCount; // Now we can use the next set of semaphores +} + + +struct QNXWindow +{ + screen_context_t Context = nullptr; + screen_window_t Window = nullptr; + screen_event_t Event = nullptr; + int Size[2] = {}; +}; + +static bool CreateQNXWindow(QNXWindow* window) +{ + if (screen_create_context(&window->Context, 0) != 0 || + screen_create_window(&window->Window, window->Context) != 0 || + screen_create_event(&window->Event) != 0) + { + fprintf(stderr, "QNX Screen initialization failed: %s\n", strerror(errno)); + return false; + } + + // A newly created QNX Screen window reports the fullscreen dimensions. + if (screen_get_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || window->Size[0] <= 0 || window->Size[1] <= 0) + { + fprintf(stderr, "Failed to obtain QNX Screen window size: %s\n", strerror(errno)); + return false; + } + + const int usage = SCREEN_USAGE_VULKAN | SCREEN_USAGE_NATIVE; + const int transparency = SCREEN_TRANSPARENCY_NONE; + if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_USAGE, &usage) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_TRANSPARENCY, &transparency) != 0) + { + fprintf(stderr, "Failed to configure QNX Screen window: %s\n", strerror(errno)); + return false; + } + return true; +} + +static void DestroyQNXWindow(QNXWindow* window) +{ + if (window->Event != nullptr) + screen_destroy_event(window->Event); + if (window->Window != nullptr) + screen_destroy_window(window->Window); + if (window->Context != nullptr) + screen_destroy_context(window->Context); +} + +static bool ProcessQNXEvents(QNXWindow* window) +{ + for (;;) + { + if (screen_get_event(window->Context, window->Event, 0) != 0) + return false; + + int type = SCREEN_EVENT_NONE; + if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_TYPE, &type) != 0) + return false; + if (type == SCREEN_EVENT_NONE) + return true; + + ImGui_ImplQNX_ProcessEvent(window->Event); + if (type == SCREEN_EVENT_CLOSE) + return false; + if (type == SCREEN_EVENT_KEYBOARD) + { + int flags = 0; + int key_cap = 0; + if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_FLAGS, &flags) == 0 && + screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_KEY_CAP, &key_cap) == 0 && + (flags & SCREEN_FLAG_KEY_DOWN) != 0 && key_cap == KEYCODE_ESCAPE) + return false; + } + } +} + +static void QNXSleep(int milliseconds) +{ + struct timespec request = { milliseconds / 1000, (milliseconds % 1000) * 1000000L }; + nanosleep(&request, nullptr); +} + +// Main code +int main(int, char**) +{ + // Create window with Vulkan graphics context + QNXWindow window; + if (!CreateQNXWindow(&window)) + { + DestroyQNXWindow(&window); + return 1; + } + float main_scale = 1.0f; + + ImVector extensions; + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_QNX_SCREEN_SURFACE_EXTENSION_NAME); + SetupVulkan(extensions); + + // Create Window Surface + VkScreenSurfaceCreateInfoQNX surface_create_info = {}; + surface_create_info.sType = VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX; + surface_create_info.context = window.Context; + surface_create_info.window = window.Window; + VkSurfaceKHR surface; + VkResult err = vkCreateScreenSurfaceQNX(g_Instance, &surface_create_info, g_Allocator, &surface); + check_vk_result(err); + + // Create Framebuffers + int w = window.Size[0]; + int h = window.Size[1]; + ImGui_ImplVulkanH_Window* wd = &g_MainWindowData; + SetupVulkanWindow(wd, surface, w, h); + + const int visible = 1; + if (screen_set_window_property_iv(window.Window, SCREEN_PROPERTY_VISIBLE, &visible) != 0) + { + fprintf(stderr, "Failed to show QNX Screen window: %s\n", strerror(errno)); + return 1; + } + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) + + // Setup Platform/Renderer backends + ImGui_ImplQNX_Init(window.Context, window.Window); + ImGui_ImplVulkan_InitInfo init_info = {}; + //init_info.ApiVersion = VK_API_VERSION_1_3; // Pass in your value of VkApplicationInfo::apiVersion, otherwise will default to header version. + init_info.Instance = g_Instance; + init_info.PhysicalDevice = g_PhysicalDevice; + init_info.Device = g_Device; + init_info.QueueFamily = g_QueueFamily; + init_info.Queue = g_Queue; + init_info.PipelineCache = g_PipelineCache; + init_info.DescriptorPool = g_DescriptorPool; + init_info.MinImageCount = g_MinImageCount; + init_info.ImageCount = wd->ImageCount; + init_info.Allocator = g_Allocator; + init_info.PipelineInfoMain.RenderPass = wd->RenderPass; + init_info.PipelineInfoMain.Subpass = 0; + init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; + init_info.CheckVkResultFn = check_vk_result; + ImGui_ImplVulkan_Init(&init_info); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Main loop + bool done = false; + while (!done) + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + if (!ProcessQNXEvents(&window)) + done = true; + + // Resize swap chain? + int framebuffer_size[2] = {}; + if (screen_get_window_property_iv(window.Window, SCREEN_PROPERTY_SIZE, framebuffer_size) != 0) + done = true; + int fb_width = framebuffer_size[0]; + int fb_height = framebuffer_size[1]; + if (fb_width <= 0 || fb_height <= 0) + { + QNXSleep(10); + continue; + } + if (fb_width > 0 && fb_height > 0 && (g_SwapChainRebuild || g_MainWindowData.Width != fb_width || g_MainWindowData.Height != fb_height)) + { + ImGui_ImplVulkan_SetMinImageCount(g_MinImageCount); + ImGui_ImplVulkanH_CreateOrResizeWindow(g_Instance, g_PhysicalDevice, g_Device, wd, g_QueueFamily, g_Allocator, fb_width, fb_height, g_MinImageCount, 0); + g_MainWindowData.FrameIndex = 0; + g_SwapChainRebuild = false; + } + + // Start the Dear ImGui frame + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplQNX_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + const bool is_minimized = (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f); + if (!is_minimized) + { + wd->ClearValue.color.float32[0] = clear_color.x * clear_color.w; + wd->ClearValue.color.float32[1] = clear_color.y * clear_color.w; + wd->ClearValue.color.float32[2] = clear_color.z * clear_color.w; + wd->ClearValue.color.float32[3] = clear_color.w; + FrameRender(wd, draw_data); + FramePresent(wd); + } + } + + // Cleanup + err = vkDeviceWaitIdle(g_Device); + check_vk_result(err); + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplQNX_Shutdown(); + ImGui::DestroyContext(); + + CleanupVulkanWindow(&g_MainWindowData); + CleanupVulkan(); + + DestroyQNXWindow(&window); + + return 0; +} diff --git a/examples/example_qnx_vulkan/nto/Makefile b/examples/example_qnx_vulkan/nto/Makefile new file mode 100644 index 000000000..0cc5eae2d --- /dev/null +++ b/examples/example_qnx_vulkan/nto/Makefile @@ -0,0 +1,8 @@ +LIST=CPU +ifndef QRECURSE +QRECURSE=recurse.mk +ifdef QCONFIG +QRDIR=$(dir $(QCONFIG)) +endif +endif +include $(QRDIR)$(QRECURSE) diff --git a/examples/example_qnx_vulkan/nto/aarch64/Makefile b/examples/example_qnx_vulkan/nto/aarch64/Makefile new file mode 100644 index 000000000..0e22650c0 --- /dev/null +++ b/examples/example_qnx_vulkan/nto/aarch64/Makefile @@ -0,0 +1,8 @@ +LIST=VARIANT +ifndef QRECURSE +QRECURSE=recurse.mk +ifdef QCONFIG +QRDIR=$(dir $(QCONFIG)) +endif +endif +include $(QRDIR)$(QRECURSE) diff --git a/examples/example_qnx_vulkan/nto/aarch64/o.le/Makefile b/examples/example_qnx_vulkan/nto/aarch64/o.le/Makefile new file mode 100644 index 000000000..0f9d7b9a9 --- /dev/null +++ b/examples/example_qnx_vulkan/nto/aarch64/o.le/Makefile @@ -0,0 +1 @@ +include ../../../common.mk diff --git a/examples/example_qnx_vulkan/nto/x86_64/Makefile b/examples/example_qnx_vulkan/nto/x86_64/Makefile new file mode 100644 index 000000000..0e22650c0 --- /dev/null +++ b/examples/example_qnx_vulkan/nto/x86_64/Makefile @@ -0,0 +1,8 @@ +LIST=VARIANT +ifndef QRECURSE +QRECURSE=recurse.mk +ifdef QCONFIG +QRDIR=$(dir $(QCONFIG)) +endif +endif +include $(QRDIR)$(QRECURSE) diff --git a/examples/example_qnx_vulkan/nto/x86_64/o/Makefile b/examples/example_qnx_vulkan/nto/x86_64/o/Makefile new file mode 100644 index 000000000..0f9d7b9a9 --- /dev/null +++ b/examples/example_qnx_vulkan/nto/x86_64/o/Makefile @@ -0,0 +1 @@ +include ../../../common.mk From 7c9f0c148c00874d03688eeba8099f45d4b4e8a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 4 Aug 2026 14:11:12 +0200 Subject: [PATCH 162/254] Backends, Examples: QNX: amends. Moved ignore list to main file. Removed .use files. (#9492) Moved functions in example_qnx_opengl3/main.cpp to match structure of other examples. --- .gitignore | 7 + backends/imgui_impl_qnx.cpp | 6 +- backends/imgui_impl_qnx.h | 1 - examples/example_qnx_opengl3/.gitignore | 19 -- .../example_qnx_opengl3.use | 6 - examples/example_qnx_opengl3/main.cpp | 270 +++++++++--------- examples/example_qnx_vulkan/.gitignore | 19 -- .../example_qnx_vulkan/example_qnx_vulkan.use | 7 - 8 files changed, 147 insertions(+), 188 deletions(-) delete mode 100644 examples/example_qnx_opengl3/.gitignore delete mode 100644 examples/example_qnx_opengl3/example_qnx_opengl3.use delete mode 100644 examples/example_qnx_vulkan/.gitignore delete mode 100644 examples/example_qnx_vulkan/example_qnx_vulkan.use diff --git a/.gitignore b/.gitignore index 83c0ab423..250e7eed5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ imgui*.ini ## General build artifacts *.o +*.so* +*.a *.obj *.exe examples/*/Debug/* @@ -57,6 +59,11 @@ examples/example_sdl3_wgpu/web/* .idea cmake-build-* +## QNX artifacts +*.pinfo +*.dep +*.gcno + ## Unix executables from our example Makefiles examples/example_apple_metal/example_apple_metal examples/example_apple_opengl2/example_apple_opengl2 diff --git a/backends/imgui_impl_qnx.cpp b/backends/imgui_impl_qnx.cpp index 82c9cb376..3730f54d2 100644 --- a/backends/imgui_impl_qnx.cpp +++ b/backends/imgui_impl_qnx.cpp @@ -14,11 +14,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-07-30: Renamed backend from imgui_impl_screen to imgui_impl_qnx and embedded a compact keysym table. -// 2026-07-29: Reworked keyboard handling from the proven qterm Screen path: authoritative modifier masks, -// complete keypad/Hyper mapping, and QNX/X11 keysym-to-Unicode conversion. -// 2026-07-29: Fixed keyboard handling: use KEY_CAP for physical keys, SYM for text, and honor Screen validity flags. -// 2026-07-28: Initial QNX Screen backend. +// 2026-08-04: Initial QNX Screen backend. (#9492) #include "imgui.h" #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_qnx.h b/backends/imgui_impl_qnx.h index 6436dfea9..7bd86f439 100644 --- a/backends/imgui_impl_qnx.h +++ b/backends/imgui_impl_qnx.h @@ -10,7 +10,6 @@ // [ ] Platform: Clipboard support. // [ ] Platform: Gamepad support. // [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). -// [ ] Platform: Multi-viewport support. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/examples/example_qnx_opengl3/.gitignore b/examples/example_qnx_opengl3/.gitignore deleted file mode 100644 index 12b50871c..000000000 --- a/examples/example_qnx_opengl3/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# ignore all files -* - -# unignore all dirs -!/**/ - -# unignore all with extensions -!*.* - -# unignore makefiles -!Makefile - -# re-ignore qnx build artefacts -*.o -*.so* -*.a -*.pinfo -*.dep -*.gcno diff --git a/examples/example_qnx_opengl3/example_qnx_opengl3.use b/examples/example_qnx_opengl3/example_qnx_opengl3.use deleted file mode 100644 index 6361d35cc..000000000 --- a/examples/example_qnx_opengl3/example_qnx_opengl3.use +++ /dev/null @@ -1,6 +0,0 @@ -Dear ImGui QNX Screen + OpenGL ES 3 example - -Usage: example_qnx_opengl3 - -The example uses the fullscreen size reported by QNX Screen, displays the Dear ImGui demo UI, -and exits when Escape is pressed or the Screen window receives a close event. diff --git a/examples/example_qnx_opengl3/main.cpp b/examples/example_qnx_opengl3/main.cpp index 76cc3a866..d78bee90c 100644 --- a/examples/example_qnx_opengl3/main.cpp +++ b/examples/example_qnx_opengl3/main.cpp @@ -36,137 +36,10 @@ struct QNXWindow int Size[2] = {}; }; -static bool CreateQNXWindow(QNXWindow* window) -{ - if (screen_create_context(&window->Context, 0) != 0 || - screen_create_window(&window->Window, window->Context) != 0 || - screen_create_event(&window->Event) != 0) - { - fprintf(stderr, "QNX Screen initialization failed: %s\n", strerror(errno)); - return false; - } - - // A newly created QNX Screen window reports the fullscreen dimensions. - if (screen_get_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || window->Size[0] <= 0 || window->Size[1] <= 0) - { - fprintf(stderr, "Failed to obtain QNX Screen window size: %s\n", strerror(errno)); - return false; - } - - const int usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_OPENGL_ES3 | SCREEN_USAGE_NATIVE; - const int transparency = SCREEN_TRANSPARENCY_NONE; - const int swap_interval = 1; - if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_USAGE, &usage) != 0 || - screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || - screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_TRANSPARENCY, &transparency) != 0 || - screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SWAP_INTERVAL, &swap_interval) != 0) - { - fprintf(stderr, "Failed to configure QNX Screen window: %s\n", strerror(errno)); - return false; - } - - window->Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (window->Display == EGL_NO_DISPLAY || eglInitialize(window->Display, nullptr, nullptr) != EGL_TRUE || eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) - { - fprintf(stderr, "EGL initialization failed: 0x%04x\n", eglGetError()); - return false; - } - - const EGLint config_attributes[] = - { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 24, - EGL_STENCIL_SIZE, 8, - EGL_NONE - }; - EGLConfig config = nullptr; - EGLint config_count = 0; - if (eglChooseConfig(window->Display, config_attributes, &config, 1, &config_count) != EGL_TRUE || config_count == 0) - { - fprintf(stderr, "eglChooseConfig() failed: 0x%04x\n", eglGetError()); - return false; - } - - EGLint native_visual = 0; - if (eglGetConfigAttrib(window->Display, config, EGL_NATIVE_VISUAL_ID, &native_visual) != EGL_TRUE || - screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_FORMAT, &native_visual) != 0 || - screen_create_window_buffers(window->Window, 2) != 0) - { - fprintf(stderr, "Failed to create QNX Screen buffers: %s\n", strerror(errno)); - return false; - } - - const EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; - window->GLContext = eglCreateContext(window->Display, config, EGL_NO_CONTEXT, context_attributes); - window->GLSurface = eglCreateWindowSurface(window->Display, config, window->Window, nullptr); - if (window->GLContext == EGL_NO_CONTEXT || window->GLSurface == EGL_NO_SURFACE || - eglMakeCurrent(window->Display, window->GLSurface, window->GLSurface, window->GLContext) != EGL_TRUE || - eglSwapInterval(window->Display, 1) != EGL_TRUE) - { - fprintf(stderr, "EGL context creation failed: 0x%04x\n", eglGetError()); - return false; - } - - const int visible = 1; - if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_VISIBLE, &visible) != 0) - { - fprintf(stderr, "Failed to show QNX Screen window: %s\n", strerror(errno)); - return false; - } - return true; -} - -static void DestroyQNXWindow(QNXWindow* window) -{ - if (window->Display != EGL_NO_DISPLAY) - { - eglMakeCurrent(window->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (window->GLSurface != EGL_NO_SURFACE) - eglDestroySurface(window->Display, window->GLSurface); - if (window->GLContext != EGL_NO_CONTEXT) - eglDestroyContext(window->Display, window->GLContext); - eglTerminate(window->Display); - } - if (window->Event != nullptr) - screen_destroy_event(window->Event); - if (window->Window != nullptr) - screen_destroy_window(window->Window); - if (window->Context != nullptr) - screen_destroy_context(window->Context); -} - -static bool ProcessQNXEvents(QNXWindow* window) -{ - for (;;) - { - if (screen_get_event(window->Context, window->Event, 0) != 0) - return false; - - int type = SCREEN_EVENT_NONE; - if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_TYPE, &type) != 0) - return false; - if (type == SCREEN_EVENT_NONE) - return true; - - ImGui_ImplQNX_ProcessEvent(window->Event); - if (type == SCREEN_EVENT_CLOSE) - return false; - if (type == SCREEN_EVENT_KEYBOARD) - { - int flags = 0; - int key_cap = 0; - if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_FLAGS, &flags) == 0 && - screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_KEY_CAP, &key_cap) == 0 && - (flags & SCREEN_FLAG_KEY_DOWN) != 0 && key_cap == KEYCODE_ESCAPE) - return false; - } - } -} +// Forward declarations of helper functions +bool CreateQNXWindow(QNXWindow* window); +void DestroyQNXWindow(QNXWindow* window); +bool ProcessQNXEvents(QNXWindow* window); // Main code int main(int, char**) @@ -297,3 +170,138 @@ int main(int, char**) return 0; } + +// Helper functions + +bool CreateQNXWindow(QNXWindow* window) +{ + if (screen_create_context(&window->Context, 0) != 0 || + screen_create_window(&window->Window, window->Context) != 0 || + screen_create_event(&window->Event) != 0) + { + fprintf(stderr, "QNX Screen initialization failed: %s\n", strerror(errno)); + return false; + } + + // A newly created QNX Screen window reports the fullscreen dimensions. + if (screen_get_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || window->Size[0] <= 0 || window->Size[1] <= 0) + { + fprintf(stderr, "Failed to obtain QNX Screen window size: %s\n", strerror(errno)); + return false; + } + + const int usage = SCREEN_USAGE_OPENGL_ES2 | SCREEN_USAGE_OPENGL_ES3 | SCREEN_USAGE_NATIVE; + const int transparency = SCREEN_TRANSPARENCY_NONE; + const int swap_interval = 1; + if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_USAGE, &usage) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SIZE, window->Size) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_TRANSPARENCY, &transparency) != 0 || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_SWAP_INTERVAL, &swap_interval) != 0) + { + fprintf(stderr, "Failed to configure QNX Screen window: %s\n", strerror(errno)); + return false; + } + + window->Display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (window->Display == EGL_NO_DISPLAY || eglInitialize(window->Display, nullptr, nullptr) != EGL_TRUE || eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE) + { + fprintf(stderr, "EGL initialization failed: 0x%04x\n", eglGetError()); + return false; + } + + const EGLint config_attributes[] = + { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_NONE + }; + EGLConfig config = nullptr; + EGLint config_count = 0; + if (eglChooseConfig(window->Display, config_attributes, &config, 1, &config_count) != EGL_TRUE || config_count == 0) + { + fprintf(stderr, "eglChooseConfig() failed: 0x%04x\n", eglGetError()); + return false; + } + + EGLint native_visual = 0; + if (eglGetConfigAttrib(window->Display, config, EGL_NATIVE_VISUAL_ID, &native_visual) != EGL_TRUE || + screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_FORMAT, &native_visual) != 0 || + screen_create_window_buffers(window->Window, 2) != 0) + { + fprintf(stderr, "Failed to create QNX Screen buffers: %s\n", strerror(errno)); + return false; + } + + const EGLint context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + window->GLContext = eglCreateContext(window->Display, config, EGL_NO_CONTEXT, context_attributes); + window->GLSurface = eglCreateWindowSurface(window->Display, config, window->Window, nullptr); + if (window->GLContext == EGL_NO_CONTEXT || window->GLSurface == EGL_NO_SURFACE || + eglMakeCurrent(window->Display, window->GLSurface, window->GLSurface, window->GLContext) != EGL_TRUE || + eglSwapInterval(window->Display, 1) != EGL_TRUE) + { + fprintf(stderr, "EGL context creation failed: 0x%04x\n", eglGetError()); + return false; + } + + const int visible = 1; + if (screen_set_window_property_iv(window->Window, SCREEN_PROPERTY_VISIBLE, &visible) != 0) + { + fprintf(stderr, "Failed to show QNX Screen window: %s\n", strerror(errno)); + return false; + } + return true; +} + +void DestroyQNXWindow(QNXWindow* window) +{ + if (window->Display != EGL_NO_DISPLAY) + { + eglMakeCurrent(window->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (window->GLSurface != EGL_NO_SURFACE) + eglDestroySurface(window->Display, window->GLSurface); + if (window->GLContext != EGL_NO_CONTEXT) + eglDestroyContext(window->Display, window->GLContext); + eglTerminate(window->Display); + } + if (window->Event != nullptr) + screen_destroy_event(window->Event); + if (window->Window != nullptr) + screen_destroy_window(window->Window); + if (window->Context != nullptr) + screen_destroy_context(window->Context); +} + +bool ProcessQNXEvents(QNXWindow* window) +{ + for (;;) + { + if (screen_get_event(window->Context, window->Event, 0) != 0) + return false; + + int type = SCREEN_EVENT_NONE; + if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_TYPE, &type) != 0) + return false; + if (type == SCREEN_EVENT_NONE) + return true; + + ImGui_ImplQNX_ProcessEvent(window->Event); + if (type == SCREEN_EVENT_CLOSE) + return false; + if (type == SCREEN_EVENT_KEYBOARD) + { + int flags = 0; + int key_cap = 0; + if (screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_FLAGS, &flags) == 0 && + screen_get_event_property_iv(window->Event, SCREEN_PROPERTY_KEY_CAP, &key_cap) == 0 && + (flags & SCREEN_FLAG_KEY_DOWN) != 0 && key_cap == KEYCODE_ESCAPE) + return false; + } + } +} + diff --git a/examples/example_qnx_vulkan/.gitignore b/examples/example_qnx_vulkan/.gitignore deleted file mode 100644 index 12b50871c..000000000 --- a/examples/example_qnx_vulkan/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# ignore all files -* - -# unignore all dirs -!/**/ - -# unignore all with extensions -!*.* - -# unignore makefiles -!Makefile - -# re-ignore qnx build artefacts -*.o -*.so* -*.a -*.pinfo -*.dep -*.gcno diff --git a/examples/example_qnx_vulkan/example_qnx_vulkan.use b/examples/example_qnx_vulkan/example_qnx_vulkan.use deleted file mode 100644 index 893eb5c41..000000000 --- a/examples/example_qnx_vulkan/example_qnx_vulkan.use +++ /dev/null @@ -1,7 +0,0 @@ -Dear ImGui QNX Screen + Vulkan example - -Usage: example_qnx_vulkan - -The example uses the fullscreen size reported by QNX Screen, creates a VK_QNX_screen_surface -swapchain, displays the Dear ImGui demo UI, and exits when Escape is pressed -or the Screen window receives a close event. From 2de0a07cd4b59416d0318a5952f5f5594955dd10 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 4 Aug 2026 14:33:31 +0200 Subject: [PATCH 163/254] Fixed PVS Studio false positive. --- imgui.cpp | 2 +- imgui_draw.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 2eee9d87f..e8b16a7fa 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8228,7 +8228,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Set default BgClickFlags // This is set at the end of this function, so UpdateWindowManualResize()/ClampWindowPos() may use last-frame value if overridden by user code. // FIXME: The general intent is that we will later expose config options to default to enable scrolling + select scrolling mouse button. - window->BgClickFlags = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->BgClickFlags : (g.IO.ConfigWindowsMoveFromTitleBarOnly ? ImGuiWindowBgClickFlags_None : ImGuiWindowBgClickFlags_Move); + window->BgClickFlags = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->BgClickFlags : (g.IO.ConfigWindowsMoveFromTitleBarOnly ? ImGuiWindowBgClickFlags_None : ImGuiWindowBgClickFlags_Move); // -V1004 // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a9759c072..35ac29f47 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -722,6 +722,7 @@ void ImDrawList::_SetTexture(ImTextureRef tex_ref) void ImDrawList::_SetPixelDensity(float pixel_density) { + IM_ASSERT(pixel_density > 0.0f); _FringeScale = 1.0f / pixel_density; _InvFringeScale = pixel_density; } From a5a3e1bd0490d854ad1244bdc41152a488eafb4a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Aug 2026 14:58:55 +0200 Subject: [PATCH 164/254] (Breaking) Backends: DX12: removed support for legacy `ImGui_ImplDX12_Init()` signature obsoleted in 1.91.6. (#9487) Amend 40b2286d1, 142827f7, eefe5d5aa. --- backends/imgui_impl_dx12.cpp | 32 +------------------------------- backends/imgui_impl_dx12.h | 15 ++++++++------- docs/CHANGELOG.txt | 4 ++++ 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 034882856..04516eefd 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-08-05: DirectX12: *BREAKING CHANGE* Removed support for legacy `ImGui_ImplDX12_Init()` signature obsoleted in 1.91.6 (2024-11-15) because it needed to forcefully disable support for ImGuiBackendFlags_RendererHasTextures. (#9487) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2025-10-11: DirectX12: Reuse texture upload buffer and grow it only when necessary. (#9002) // 2025-09-29: DirectX12: Rework synchronization logic. (#8961) @@ -956,37 +957,6 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) return true; } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -// Legacy initialization API Obsoleted in 1.91.5 -// font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' -bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle) -{ - ImGui_ImplDX12_InitInfo init_info; - init_info.Device = device; - init_info.NumFramesInFlight = num_frames_in_flight; - init_info.RTVFormat = rtv_format; - init_info.SrvDescriptorHeap = srv_descriptor_heap; - init_info.LegacySingleSrvCpuDescriptor = font_srv_cpu_desc_handle; - init_info.LegacySingleSrvGpuDescriptor = font_srv_gpu_desc_handle; - - D3D12_COMMAND_QUEUE_DESC queueDesc = {}; - queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; - queueDesc.NodeMask = 1; - HRESULT hr = device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&init_info.CommandQueue)); - IM_ASSERT(SUCCEEDED(hr)); - - bool ret = ImGui_ImplDX12_Init(&init_info); - ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - bd->commandQueueOwned = true; - - ImGuiIO& io = ImGui::GetIO(); - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures; // Using legacy ImGui_ImplDX12_Init() call with 1 SRV descriptor we cannot support multiple textures. - - return ret; -} -#endif - void ImGui_ImplDX12_Shutdown() { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index b8be8db22..dfb327fe4 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -59,13 +59,6 @@ IMGUI_IMPL_API void ImGui_ImplDX12_Shutdown(); IMGUI_IMPL_API void ImGui_ImplDX12_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandList* graphics_command_list); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -// Legacy initialization API Obsoleted in 1.91.5 -// - font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' -// - When we introduced the ImGui_ImplDX12_InitInfo struct we also added a 'ID3D12CommandQueue* CommandQueue' field. -IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle); -#endif - // Use if you want to reset your rendering device without losing Dear ImGui state. IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); @@ -82,6 +75,14 @@ struct ImGui_ImplDX12_RenderState ID3D12GraphicsCommandList* CommandList; }; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Legacy initialization API Obsoleted in 1.91.5 +// - font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' +// - When we introduced the ImGui_ImplDX12_InitInfo struct we also added a 'ID3D12CommandQueue* CommandQueue' field. +// - DOES NOT SUPPORT ImGuiBackendFlags_RendererHasTextures. +//IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FORMAT rtv_format, ID3D12DescriptorHeap* srv_descriptor_heap, D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle); +#endif + #if defined(__clang__) #pragma clang diagnostic pop #endif diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3197f28e6..a4ed837f5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -45,6 +45,10 @@ Breaking Changes: favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. It is easier to tweak + this allows us to easily scale error threshold on high density screens. - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). +- Backends: + - DirectX12: Removed support for legacy `ImGui_ImplDX12_Init()` signature, which was + obsoleted in 1.91.6 (December 2014), because it needed to forcefully disable support + for `ImGuiBackendFlags_RendererHasTextures`. Use the new signature! (#9487) Other Changes: From 6a6d7bc3ca5f6a88517177988f4b8b8caf3b3822 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Aug 2026 20:38:18 +0200 Subject: [PATCH 165/254] Internals: added SetNextWindowFlags(), SetNextWindowChildFlags() helpers + made BeginChildEx() itself read from them. (#9503, #8280, #9412, #9499, #9393, #8488) As a result BeginTable() doesn't need to read itself and pass that to BeginChildEx(). --- imgui.cpp | 44 +++++++++++++++++++++++++++++--------------- imgui.h | 2 +- imgui_internal.h | 8 ++++++-- imgui_tables.cpp | 7 ++----- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e8b16a7fa..d157353cf 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6537,6 +6537,9 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I ImGuiWindow* parent_window = g.CurrentWindow; IM_ASSERT(id != 0); + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) + child_flags = (child_flags | g.NextWindowData.ChildFlagsSet) & ~g.NextWindowData.ChildFlagsClear; + // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument. const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened; IM_UNUSED(ImGuiChildFlags_SupportedMask_); @@ -6599,13 +6602,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I } } SetNextWindowSize(size); - - // Forward child flags (we allow prior settings to merge but it'll only work for adding flags) - if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) - g.NextWindowData.ChildFlags |= child_flags; - else - g.NextWindowData.ChildFlags = child_flags; - g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; + SetNextWindowChildFlags(child_flags, true); // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. @@ -7550,6 +7547,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (g.DebugBreakInWindow == window->ID) IM_DEBUG_BREAK(); + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) + flags = (flags | g.NextWindowData.WindowFlagsSet) & ~g.NextWindowData.WindowFlagsClear; + // Automatically disable manual moving/resizing when NoInputs is set if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; @@ -7575,7 +7575,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { UpdateWindowInFocusOrderList(window, window_just_created, flags); window->Flags = (ImGuiWindowFlags)flags; - window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0; + window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlagsSet : 0; window->LastFrameActive = current_frame; window->LastTimeActive = (float)g.Time; window->BeginOrderWithinParent = 0; @@ -8834,6 +8834,26 @@ void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) g.NextWindowData.RefreshFlagsVal = flags; } +void ImGui::SetNextWindowFlags(ImGuiWindowFlags flags, bool enabled) +{ + ImGuiContext& g = *GImGui; + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) == 0) + g.NextWindowData.WindowFlagsSet = g.NextWindowData.WindowFlagsClear = ImGuiChildFlags_None; + g.NextWindowData.WindowFlagsSet = enabled ? (g.NextWindowData.WindowFlagsSet | flags) : (g.NextWindowData.WindowFlagsSet & ~flags); + g.NextWindowData.WindowFlagsClear = !enabled ? (g.NextWindowData.WindowFlagsClear | flags) : (g.NextWindowData.WindowFlagsClear & ~flags); + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasWindowFlags; +} + +void ImGui::SetNextWindowChildFlags(ImGuiChildFlags flags, bool enabled) +{ + ImGuiContext& g = *GImGui; + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) == 0) + g.NextWindowData.ChildFlagsSet = g.NextWindowData.ChildFlagsClear = ImGuiChildFlags_None; + g.NextWindowData.ChildFlagsSet = enabled ? (g.NextWindowData.ChildFlagsSet | flags) : (g.NextWindowData.ChildFlagsSet & ~flags); + g.NextWindowData.ChildFlagsClear = !enabled ? (g.NextWindowData.ChildFlagsClear | flags) : (g.NextWindowData.ChildFlagsClear & ~flags); + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; +} + ImDrawList* ImGui::GetWindowDrawList() { ImGuiWindow* window = GetCurrentWindow(); @@ -12674,13 +12694,7 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext // As we bypass BeginChild(), set ImGuiChildFlags_AlwaysAutoResize as it is checked independently from ImGuiWindowFlags_AlwaysAutoResize for now (see #9355) // Ideally we should remove setting ImGuiWindowFlags_AlwaysAutoResize in BeginChild(). if ((extra_window_flags & ImGuiWindowFlags_ChildWindow) && (extra_window_flags & ImGuiWindowFlags_AlwaysAutoResize)) - { - if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) - g.NextWindowData.ChildFlags |= ImGuiChildFlags_AlwaysAutoResize; - else - g.NextWindowData.ChildFlags = ImGuiChildFlags_AlwaysAutoResize; - g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; - } + SetNextWindowChildFlags(ImGuiChildFlags_AlwaysAutoResize, true); char name[128]; IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); diff --git a/imgui.h b/imgui.h index fea82515d..43f297d44 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.93.0 WIP" -#define IMGUI_VERSION_NUM 19292 +#define IMGUI_VERSION_NUM 19293 #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_internal.h b/imgui_internal.h index 59f005508..d837a6962 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1360,8 +1360,10 @@ struct ImGuiNextWindowData ImVec2 SizeVal; ImVec2 ContentSizeVal; ImVec2 ScrollVal; - ImGuiWindowFlags WindowFlags; // Only honored by BeginTable() - ImGuiChildFlags ChildFlags; + ImGuiWindowFlags WindowFlagsSet; // Flags to set. Use SetNextWindowFlags()! + ImGuiWindowFlags WindowFlagsClear; + ImGuiChildFlags ChildFlagsSet; // Flags to set. Use SetNextWindowChildFlags(). + ImGuiChildFlags ChildFlagsClear; bool CollapsedVal; ImRect SizeConstraintRect; ImGuiSizeCallback SizeCallback; @@ -3368,6 +3370,8 @@ namespace ImGui inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } inline ImVec2 WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } + IMGUI_API void SetNextWindowFlags(ImGuiWindowFlags flags, bool enabled); + IMGUI_API void SetNextWindowChildFlags(ImGuiChildFlags flags, bool enabled); // Windows: Display Order and Focus Order IMGUI_API void FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 19f7933ed..8f260c873 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -428,11 +428,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG SetNextWindowScroll(ImVec2(0.0f, 0.0f)); // Create scrolling region (without border and zero window padding) - ImGuiChildFlags child_child_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : ImGuiChildFlags_None; - ImGuiWindowFlags child_window_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) ? g.NextWindowData.WindowFlags : ImGuiWindowFlags_None; - if (flags & ImGuiTableFlags_ScrollX) - child_window_flags |= ImGuiWindowFlags_HorizontalScrollbar; - BeginChildEx(name, instance_id, outer_rect.GetSize(), child_child_flags, child_window_flags); + ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, outer_rect.GetSize(), ImGuiChildFlags_None, child_window_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); From 724c69b182f1804dfeb651d2d176b284d77067fb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 18:28:23 +0200 Subject: [PATCH 166/254] Backends: SDL2: fixed querying framebuffer scale/density when using Metal. (#5592) --- backends/imgui_impl_sdl2.cpp | 13 +++++++++++++ docs/CHANGELOG.txt | 1 + examples/example_sdl2_metal/main.mm | 8 +++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index eec4e0075..a93bcc29e 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-08-06: Fixed querying framebuffer scale when using Metal. (#5592) // 2026-07-15: Inputs: restore SDL_StartTextInput()/SDL_StopTextInput() in IME handler for on-screen keyboard support on Android/mobile. (#7636, #9474) // 2026-04-16: Made ImGui_ImplSDL2_GetContentScaleForWindow(), ImGui_ImplSDL2_GetContentScaleForDisplay() helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) // 2026-02-13: Inputs: systems other than X11 are back to starting mouse capture on mouse down (reverts 2025-02-26 change). Only X11 requires waiting for a drag by default (not ideal, but a better default for X11 users). Added ImGui_ImplSDL2_SetMouseCaptureMode() for X11 debugger users. (#3650, #6410, #9235) @@ -135,10 +136,18 @@ #endif #define SDL_HAS_PER_MONITOR_DPI SDL_VERSION_ATLEAST(2,0,4) #define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6) +#if defined(__APPLE__) +#define SDL_HAS_METAL SDL_VERSION_ATLEAST(2,0,14) +#else +#define SDL_HAS_METAL 0 +#endif #define SDL_HAS_OPEN_URL SDL_VERSION_ATLEAST(2,0,14) #if SDL_HAS_VULKAN #include #endif +#if SDL_HAS_METAL +#include +#endif // SDL Data struct ImGui_ImplSDL2_Data @@ -899,6 +908,10 @@ static void ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(SDL_Window* window, #if SDL_HAS_VULKAN else if (SDL_GetWindowFlags(window) & SDL_WINDOW_VULKAN) SDL_Vulkan_GetDrawableSize(window, &display_w, &display_h); +#endif +#if SDL_HAS_METAL + else if (SDL_GetWindowFlags(window) & SDL_WINDOW_METAL) + SDL_Metal_GetDrawableSize(window, &display_w, &display_h); #endif else SDL_GL_GetDrawableSize(window, &display_w, &display_h); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a4ed837f5..994b0c4a2 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,6 +62,7 @@ Other Changes: - Circle and Curves tessellation error are scaled accordingly. - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - SDL2: fixed querying framebuffer scale/density when using Metal. (#5592) [@lailoken] - Examples: - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] diff --git a/examples/example_sdl2_metal/main.mm b/examples/example_sdl2_metal/main.mm index ea2afdab3..22d0e0fc8 100644 --- a/examples/example_sdl2_metal/main.mm +++ b/examples/example_sdl2_metal/main.mm @@ -56,13 +56,15 @@ int main(int, char**) return 1; } - // Inform SDL that we will be using metal for rendering. Without this hint initialization of metal renderer may fail. + // Inform SDL that we will be using metal for rendering. Without this hint initialization of Metal renderer may fail. SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); - // Enable native IME. + // From 2.0.18: Enable native IME. +#ifdef SDL_HINT_IME_SHOW_UI SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL+Metal example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 800, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+Metal example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 800, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); if (window == nullptr) { printf("Error creating window: %s\n", SDL_GetError()); From 730f099239a15e48caf3efedb3070d0e001c9946 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 19:01:49 +0200 Subject: [PATCH 167/254] Backends: OpenGL3: fixed unused variable warning. (#9507) --- backends/imgui_impl_opengl3.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 7be882e06..e917fd36b 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -385,6 +385,7 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, ImGui_Impl glUniform1i(bd->AttribLocationTex, 0); glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + (void)render_state; #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER if (render_state->UseBindSampler) { From 98f3efcf07929569ee73e142c4ac32e77614fa17 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 21:17:55 +0200 Subject: [PATCH 168/254] Examples: SDL2+Metal: fixed mismatches with other examples. --- examples/example_sdl2_metal/main.mm | 102 +++++++++++++++------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/examples/example_sdl2_metal/main.mm b/examples/example_sdl2_metal/main.mm index 22d0e0fc8..bae566daf 100644 --- a/examples/example_sdl2_metal/main.mm +++ b/examples/example_sdl2_metal/main.mm @@ -10,25 +10,74 @@ #include "imgui.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_metal.h" -#include +#include // printf, fprintf #include #import #import +// Main code int main(int, char**) { + // Setup SDL + // (Some versions of SDL before <2.0.10 appears to have performance/stalling issues on a minority of Windows systems, + // depending on whether SDL_INIT_GAMECONTROLLER is enabled or disabled.. updating to latest version of SDL is recommended!) + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) + { + printf("Error: SDL_Init(): %s\n", SDL_GetError()); + return 1; + } + + // Inform SDL that we will be using metal for rendering. Without this hint initialization of Metal renderer may fail. + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); + + // From 2.0.18: Enable native IME. +#ifdef SDL_HINT_IME_SHOW_UI + SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); +#endif + + // Create SDL window graphics context + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); + int window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+Metal example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(800 * main_scale), window_flags); + if (window == nullptr) + { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return 1; + } + + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (renderer == nullptr) + { + printf("Error creating renderer: %s\n", SDL_GetError()); + return -3; + } + CAMetalLayer* layer = (__bridge CAMetalLayer*)SDL_RenderGetMetalLayer(renderer); + layer.pixelFormat = MTLPixelFormatBGRA8Unorm; + + id commandQueue = [layer.device newCommandQueue]; + MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor new]; + // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - // Setup style + // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); + // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (in docking branch: using io.ConfigDpiScaleFonts=true automatically overrides this for every window depending on the current monitor) + + // Setup Platform/Renderer backends + ImGui_ImplMetal_Init(layer.device); + ImGui_ImplSDL2_InitForMetal(window); + // Load Fonts // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. @@ -47,50 +96,10 @@ int main(int, char**) //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); //IM_ASSERT(font != nullptr); - // Setup SDL - // (Some versions of SDL before <2.0.10 appears to have performance/stalling issues on a minority of Windows systems, - // depending on whether SDL_INIT_GAMECONTROLLER is enabled or disabled.. updating to latest version of SDL is recommended!) - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) - { - printf("Error: %s\n", SDL_GetError()); - return 1; - } - - // Inform SDL that we will be using metal for rendering. Without this hint initialization of Metal renderer may fail. - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); - - // From 2.0.18: Enable native IME. -#ifdef SDL_HINT_IME_SHOW_UI - SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); -#endif - - SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+Metal example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 800, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - if (window == nullptr) - { - printf("Error creating window: %s\n", SDL_GetError()); - return -2; - } - - SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - if (renderer == nullptr) - { - printf("Error creating renderer: %s\n", SDL_GetError()); - return -3; - } - - // Setup Platform/Renderer backends - CAMetalLayer* layer = (__bridge CAMetalLayer*)SDL_RenderGetMetalLayer(renderer); - layer.pixelFormat = MTLPixelFormatBGRA8Unorm; - ImGui_ImplMetal_Init(layer.device); - ImGui_ImplSDL2_InitForMetal(window); - - id commandQueue = [layer.device newCommandQueue]; - MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor new]; - // Our state bool show_demo_window = true; bool show_another_window = false; - float clear_color[4] = {0.45f, 0.55f, 0.60f, 1.00f}; + float clear_color[4] = { 0.45f, 0.55f, 0.60f, 1.00f }; // Main loop bool done = false; @@ -170,7 +179,8 @@ int main(int, char**) // Rendering ImGui::Render(); - ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), commandBuffer, renderEncoder); + ImDrawData* draw_data = ImGui::GetDrawData(); + ImGui_ImplMetal_RenderDrawData(draw_data, commandBuffer, renderEncoder); [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; From 2ddb5fc0956e6376ccdbb0a522a5a3f0d8b42696 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 21:23:28 +0200 Subject: [PATCH 169/254] Examples: SDL2+Metal: stop using SDL_CreateRenderer(). (#5592) --- docs/CHANGELOG.txt | 4 +++- examples/example_sdl2_metal/main.mm | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 994b0c4a2..2101df990 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,8 +62,10 @@ Other Changes: - Circle and Curves tessellation error are scaled accordingly. - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - - SDL2: fixed querying framebuffer scale/density when using Metal. (#5592) [@lailoken] + - SDL2: fixed querying framebuffer scale/density when using Metal without + going through a SDL_Renderer. (#5592) [@lailoken] - Examples: + - SDL2+Metal: create Metal context without using a SDL_Renderer. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] diff --git a/examples/example_sdl2_metal/main.mm b/examples/example_sdl2_metal/main.mm index bae566daf..edd436aca 100644 --- a/examples/example_sdl2_metal/main.mm +++ b/examples/example_sdl2_metal/main.mm @@ -28,9 +28,6 @@ int main(int, char**) return 1; } - // Inform SDL that we will be using metal for rendering. Without this hint initialization of Metal renderer may fail. - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); - // From 2.0.18: Enable native IME. #ifdef SDL_HINT_IME_SHOW_UI SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); @@ -46,13 +43,18 @@ int main(int, char**) return 1; } - SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); - if (renderer == nullptr) + // Create Metal device _before_ creating the view/layer + id metalDevice = MTLCreateSystemDefaultDevice(); + if (!metalDevice) { - printf("Error creating renderer: %s\n", SDL_GetError()); - return -3; + printf("Error: failed to create Metal device.\n"); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; } - CAMetalLayer* layer = (__bridge CAMetalLayer*)SDL_RenderGetMetalLayer(renderer); + SDL_MetalView view = SDL_Metal_CreateView(window); + CAMetalLayer* layer = (__bridge CAMetalLayer*)SDL_Metal_GetLayer(view); + layer.device = metalDevice; layer.pixelFormat = MTLPixelFormatBGRA8Unorm; id commandQueue = [layer.device newCommandQueue]; @@ -123,7 +125,8 @@ int main(int, char**) } int width, height; - SDL_GetRendererOutputSize(renderer, &width, &height); + SDL_GetWindowSizeInPixels(window, &width, &height); + layer.drawableSize = CGSizeMake(width, height); id drawable = [layer nextDrawable]; @@ -195,7 +198,6 @@ int main(int, char**) ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); - SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); From 70e60a3c4c1698e699cebbd1bb92404b21233cd3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 23:01:56 +0200 Subject: [PATCH 170/254] Examples: Apple+OpenGL2: silence warnings. Example is precisely to demonstrate OpenGL. --- examples/example_apple_opengl2/main.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index 1cfa882c8..65c169089 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -6,6 +6,7 @@ // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). // - Introduction, links and more at the top of imgui.cpp +#define GL_SILENCE_DEPRECATION #import #import #import @@ -32,7 +33,7 @@ #ifndef DEBUG GLint swapInterval = 1; - [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; + [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLContextParameterSwapInterval]; if (swapInterval == 0) NSLog(@"Error: Cannot set swap interval."); #endif From 5b6e19bb50359f7b0c62d2081f2ea024bd9fd5a1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 23:10:56 +0200 Subject: [PATCH 171/254] Examples: Apple+OpenGL3: added new example. --- .github/workflows/build.yml | 4 + .gitignore | 1 + docs/CHANGELOG.txt | 1 + docs/EXAMPLES.md | 6 +- examples/example_apple_opengl3/Makefile | 21 ++ .../project.pbxproj | 334 ++++++++++++++++++ examples/example_apple_opengl3/main.mm | 260 ++++++++++++++ 7 files changed, 625 insertions(+), 2 deletions(-) create mode 100644 examples/example_apple_opengl3/Makefile create mode 100644 examples/example_apple_opengl3/example_apple_opengl3.xcodeproj/project.pbxproj create mode 100644 examples/example_apple_opengl3/main.mm diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5bbaefd1a..4e276e1de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -601,6 +601,10 @@ jobs: run: xcodebuild -project examples/example_apple_opengl2/example_apple_opengl2.xcodeproj -target example_osx_opengl2 if: github.event_name == 'workflow_run' + - name: Build macOS example_apple_opengl3 + run: xcodebuild -project examples/example_apple_opengl3/example_apple_opengl3.xcodeproj -target example_osx_opengl3 + if: github.event_name == 'workflow_run' + Build-iOS: runs-on: macos-14 name: Build - iOS diff --git a/.gitignore b/.gitignore index 250e7eed5..009e61ede 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ cmake-build-* ## Unix executables from our example Makefiles examples/example_apple_metal/example_apple_metal examples/example_apple_opengl2/example_apple_opengl2 +examples/example_apple_opengl3/example_apple_opengl3 examples/example_glfw_metal/example_glfw_metal examples/example_glfw_opengl2/example_glfw_opengl2 examples/example_glfw_opengl3/example_glfw_opengl3 diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2101df990..716cd3fc7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -67,6 +67,7 @@ Other Changes: - Examples: - SDL2+Metal: create Metal context without using a SDL_Renderer. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] + - OSX+OpenGL3: added example_apple_opengl3/ (OSX/Cocoa + OpenGL 3.2 Core profile). ----------------------------------------------------------------------- diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 89e9bd0df..231fe7f93 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -70,8 +70,10 @@ OSX & iOS + Metal4 example, Mac only.
[example_apple_opengl2/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_opengl2/)
OSX + OpenGL2 example.
= main.mm + imgui_impl_osx.mm + imgui_impl_opengl2.cpp
-(NB: imgui_impl_osx.mm is currently not as feature complete as other platforms backends. - You may prefer to use the GLFW Or SDL backends, which will also support Windows and Linux.) + +[example_apple_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_apple_opengl3/)
+OSX + OpenGL3 example.
+= main.mm + imgui_impl_osx.mm + imgui_impl_opengl3.cpp
[example_glfw_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_wgpu/)
GLFW + WebGPU example. Supports Emscripten (web), Dawn (native), WGPU (native).
diff --git a/examples/example_apple_opengl3/Makefile b/examples/example_apple_opengl3/Makefile new file mode 100644 index 000000000..c9f9d44d3 --- /dev/null +++ b/examples/example_apple_opengl3/Makefile @@ -0,0 +1,21 @@ +# Makefile for example_apple_opengl3, for macOS only (**not iOS**) +CXX = clang++ +EXE = example_apple_opengl3 +IMGUI_DIR = ../../ +SOURCES = main.mm +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_osx.mm $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp + +CXXFLAGS = -std=c++11 -ObjC++ -fobjc-arc -Wall -Wextra -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +FRAMEWORKS = -framework Cocoa -framework OpenGL -framework GameController + +all: $(EXE) + +$(EXE): $(SOURCES) + $(CXX) $(CXXFLAGS) $(SOURCES) -o $(EXE) $(FRAMEWORKS) + +run: all + ./$(EXE) + +clean: + rm -f $(EXE) *.o diff --git a/examples/example_apple_opengl3/example_apple_opengl3.xcodeproj/project.pbxproj b/examples/example_apple_opengl3/example_apple_opengl3.xcodeproj/project.pbxproj new file mode 100644 index 000000000..50ee35fd9 --- /dev/null +++ b/examples/example_apple_opengl3/example_apple_opengl3.xcodeproj/project.pbxproj @@ -0,0 +1,334 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 05E31B59274EF0700083FCB6 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05E31B57274EF0360083FCB6 /* GameController.framework */; }; + 07A82EDB213941D00078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82EDA213941D00078D120 /* imgui_widgets.cpp */; }; + 4080A99820B02D340036BA46 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4080A98A20B02CD90036BA46 /* main.mm */; }; + 4080A9A220B034280036BA46 /* imgui_impl_opengl3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4080A99E20B034280036BA46 /* imgui_impl_opengl3.cpp */; }; + 4080A9AD20B0343C0036BA46 /* imgui_demo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4080A9A620B0343C0036BA46 /* imgui_demo.cpp */; }; + 4080A9AE20B0343C0036BA46 /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4080A9A720B0343C0036BA46 /* imgui.cpp */; }; + 4080A9AF20B0343C0036BA46 /* imgui_draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4080A9AA20B0343C0036BA46 /* imgui_draw.cpp */; }; + 4080A9B020B0347A0036BA46 /* imgui_impl_osx.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4080A99F20B034280036BA46 /* imgui_impl_osx.mm */; }; + 4080A9B320B034E40036BA46 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4080A9B220B034E40036BA46 /* Cocoa.framework */; }; + 4080A9B520B034EA0036BA46 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4080A9B420B034EA0036BA46 /* OpenGL.framework */; }; + 50798230257677FD0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822F257677FC0038A28D /* imgui_tables.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4080A96920B029B00036BA46 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 05E31B57274EF0360083FCB6 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; + 07A82EDA213941D00078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = ""; }; + 4080A96B20B029B00036BA46 /* example_osx_opengl3 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = example_osx_opengl3; sourceTree = BUILT_PRODUCTS_DIR; }; + 4080A98A20B02CD90036BA46 /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = SOURCE_ROOT; }; + 4080A99E20B034280036BA46 /* imgui_impl_opengl3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_impl_opengl3.cpp; path = ../../backends/imgui_impl_opengl3.cpp; sourceTree = ""; }; + 4080A99F20B034280036BA46 /* imgui_impl_osx.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = imgui_impl_osx.mm; path = ../../backends/imgui_impl_osx.mm; sourceTree = ""; }; + 4080A9A020B034280036BA46 /* imgui_impl_opengl3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_opengl3.h; path = ../../backends/imgui_impl_opengl3.h; sourceTree = ""; }; + 4080A9A120B034280036BA46 /* imgui_impl_osx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_osx.h; path = ../../backends/imgui_impl_osx.h; sourceTree = ""; }; + 4080A9A320B034280036BA46 /* imgui_impl_opengl3_loader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_impl_opengl3_loader.h; path = ../../backends/imgui_impl_opengl3_loader.h; sourceTree = ""; }; + 4080A9A520B0343C0036BA46 /* imgui_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_internal.h; path = ../../imgui_internal.h; sourceTree = ""; }; + 4080A9A620B0343C0036BA46 /* imgui_demo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_demo.cpp; path = ../../imgui_demo.cpp; sourceTree = ""; }; + 4080A9A720B0343C0036BA46 /* imgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../../imgui.cpp; sourceTree = ""; }; + 4080A9A820B0343C0036BA46 /* imgui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui.h; path = ../../imgui.h; sourceTree = ""; }; + 4080A9AA20B0343C0036BA46 /* imgui_draw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_draw.cpp; path = ../../imgui_draw.cpp; sourceTree = ""; }; + 4080A9AC20B0343C0036BA46 /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = ""; }; + 4080A9B220B034E40036BA46 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 4080A9B420B034EA0036BA46 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 5079822F257677FC0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4080A96820B029B00036BA46 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4080A9B520B034EA0036BA46 /* OpenGL.framework in Frameworks */, + 4080A9B320B034E40036BA46 /* Cocoa.framework in Frameworks */, + 05E31B59274EF0700083FCB6 /* GameController.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4080A96220B029B00036BA46 = { + isa = PBXGroup; + children = ( + 5079822F257677FC0038A28D /* imgui_tables.cpp */, + 4080A9AC20B0343C0036BA46 /* imconfig.h */, + 4080A9A720B0343C0036BA46 /* imgui.cpp */, + 4080A9A820B0343C0036BA46 /* imgui.h */, + 07A82EDA213941D00078D120 /* imgui_widgets.cpp */, + 4080A9A620B0343C0036BA46 /* imgui_demo.cpp */, + 4080A9AA20B0343C0036BA46 /* imgui_draw.cpp */, + 4080A9A520B0343C0036BA46 /* imgui_internal.h */, + 4080A99E20B034280036BA46 /* imgui_impl_opengl3.cpp */, + 4080A9A020B034280036BA46 /* imgui_impl_opengl3.h */, + 4080A9A320B034280036BA46 /* imgui_impl_opengl3_loader.h */, + 4080A9A120B034280036BA46 /* imgui_impl_osx.h */, + 4080A99F20B034280036BA46 /* imgui_impl_osx.mm */, + 4080A98A20B02CD90036BA46 /* main.mm */, + 4080A96C20B029B00036BA46 /* Products */, + 4080A9B120B034E40036BA46 /* Frameworks */, + ); + sourceTree = ""; + }; + 4080A96C20B029B00036BA46 /* Products */ = { + isa = PBXGroup; + children = ( + 4080A96B20B029B00036BA46 /* example_osx_opengl3 */, + ); + name = Products; + sourceTree = ""; + }; + 4080A9B120B034E40036BA46 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 05E31B57274EF0360083FCB6 /* GameController.framework */, + 4080A9B420B034EA0036BA46 /* OpenGL.framework */, + 4080A9B220B034E40036BA46 /* Cocoa.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4080A96A20B029B00036BA46 /* example_osx_opengl3 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4080A97220B029B00036BA46 /* Build configuration list for PBXNativeTarget "example_osx_opengl3" */; + buildPhases = ( + 4080A96720B029B00036BA46 /* Sources */, + 4080A96820B029B00036BA46 /* Frameworks */, + 4080A96920B029B00036BA46 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = example_osx_opengl3; + productName = example_osx_opengl3; + productReference = 4080A96B20B029B00036BA46 /* example_osx_opengl3 */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4080A96320B029B00036BA46 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = ImGui; + TargetAttributes = { + 4080A96A20B029B00036BA46 = { + CreatedOnToolsVersion = 9.3.1; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 4080A96620B029B00036BA46 /* Build configuration list for PBXProject "example_apple_opengl3" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 4080A96220B029B00036BA46; + productRefGroup = 4080A96C20B029B00036BA46 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4080A96A20B029B00036BA46 /* example_osx_opengl3 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 4080A96720B029B00036BA46 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4080A99820B02D340036BA46 /* main.mm in Sources */, + 4080A9AD20B0343C0036BA46 /* imgui_demo.cpp in Sources */, + 4080A9AF20B0343C0036BA46 /* imgui_draw.cpp in Sources */, + 4080A9A220B034280036BA46 /* imgui_impl_opengl3.cpp in Sources */, + 4080A9B020B0347A0036BA46 /* imgui_impl_osx.mm in Sources */, + 4080A9AE20B0343C0036BA46 /* imgui.cpp in Sources */, + 50798230257677FD0038A28D /* imgui_tables.cpp in Sources */, + 07A82EDB213941D00078D120 /* imgui_widgets.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 4080A97020B029B00036BA46 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 4080A97120B029B00036BA46 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + }; + name = Release; + }; + 4080A97320B029B00036BA46 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = ../..; + }; + name = Debug; + }; + 4080A97420B029B00036BA46 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = ../..; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4080A96620B029B00036BA46 /* Build configuration list for PBXProject "example_apple_opengl3" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4080A97020B029B00036BA46 /* Debug */, + 4080A97120B029B00036BA46 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4080A97220B029B00036BA46 /* Build configuration list for PBXNativeTarget "example_osx_opengl3" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4080A97320B029B00036BA46 /* Debug */, + 4080A97420B029B00036BA46 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4080A96320B029B00036BA46 /* Project object */; +} diff --git a/examples/example_apple_opengl3/main.mm b/examples/example_apple_opengl3/main.mm new file mode 100644 index 000000000..1541d5d07 --- /dev/null +++ b/examples/example_apple_opengl3/main.mm @@ -0,0 +1,260 @@ +// Dear ImGui: standalone example application for OSX + OpenGL3 + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - Documentation https://dearimgui.com/docs (same as your local docs/ folder). +// - Introduction, links and more at the top of imgui.cpp + +#define GL_SILENCE_DEPRECATION +#import +#import + +#include "imgui.h" +#include "imgui_impl_opengl3.h" +#include "imgui_impl_osx.h" + +//----------------------------------------------------------------------------------- +// AppView +//----------------------------------------------------------------------------------- + +@interface AppView : NSOpenGLView +{ + NSTimer* animationTimer; +} +@end + +@implementation AppView + +-(void)prepareOpenGL +{ + [super prepareOpenGL]; + +#ifndef DEBUG + GLint swapInterval = 1; + [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLContextParameterSwapInterval]; + if (swapInterval == 0) + NSLog(@"Error: Cannot set swap interval."); +#endif +} + +-(void)initialize +{ + // Setup Dear ImGui context + // FIXME: This example doesn't have proper cleanup... + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + [[self openGLContext] makeCurrentContext]; // ImGui_ImplOpenGL3_Init() requires a current GL context. + ImGui_ImplOSX_Init(self); + ImGui_ImplOpenGL3_Init(); + + // Load Fonts + // - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap(). + // This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold. + // - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit). + // - Read 'docs/FONTS.md' for more instructions and details. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefaultVector(); + //io.Fonts->AddFontDefaultBitmap(); + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //IM_ASSERT(font != nullptr); +} + +-(void)updateAndDrawDemoView +{ + // Start the Dear ImGui frame + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplOSX_NewFrame(self); + ImGui::NewFrame(); + + // Our state (make them static = more or less global) as a convenience to keep the example terse. + static bool show_demo_window = true; + static bool show_another_window = false; + static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + + [[self openGLContext] makeCurrentContext]; + GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + glViewport(0, 0, width, height); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui_ImplOpenGL3_RenderDrawData(draw_data); + + // Present + [[self openGLContext] flushBuffer]; + + if (!animationTimer) + animationTimer = [NSTimer scheduledTimerWithTimeInterval:0.017 target:self selector:@selector(animationTimerFired:) userInfo:nil repeats:YES]; +} + +-(void)reshape { [super reshape]; [[self openGLContext] update]; [self updateAndDrawDemoView]; } +-(void)drawRect:(NSRect)bounds { [self updateAndDrawDemoView]; } +-(void)animationTimerFired:(NSTimer*)timer { [self setNeedsDisplay:YES]; } +-(void)dealloc { animationTimer = nil; } + +@end + +//----------------------------------------------------------------------------------- +// AppDelegate +//----------------------------------------------------------------------------------- + +@interface AppDelegate : NSObject +@property (nonatomic, readonly) NSWindow* window; +@end + +@implementation AppDelegate +@synthesize window = _window; + +-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return YES; +} + +-(NSWindow*)window +{ + if (_window != nil) + return _window; + + NSRect viewRect = NSMakeRect(100.0, 100.0, 100.0 + 1280.0, 100 + 800.0); + + _window = [[NSWindow alloc] initWithContentRect:viewRect styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable|NSWindowStyleMaskClosable backing:NSBackingStoreBuffered defer:YES]; + [_window setTitle:@"Dear ImGui OSX+OpenGL3 Example"]; + [_window setAcceptsMouseMovedEvents:YES]; + [_window setOpaque:YES]; + [_window makeKeyAndOrderFront:NSApp]; + + return _window; +} + +-(void)setupMenu +{ + NSMenu* mainMenuBar = [[NSMenu alloc] init]; + NSMenu* appMenu; + NSMenuItem* menuItem; + + appMenu = [[NSMenu alloc] initWithTitle:@"Dear ImGui OSX+OpenGL3 Example"]; + menuItem = [appMenu addItemWithTitle:@"Quit Dear ImGui OSX+OpenGL3 Example" action:@selector(terminate:) keyEquivalent:@"q"]; + [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; + + menuItem = [[NSMenuItem alloc] init]; + [menuItem setSubmenu:appMenu]; + + [mainMenuBar addItem:menuItem]; + + appMenu = nil; + [NSApp setMainMenu:mainMenuBar]; +} + +-(void)dealloc +{ + _window = nil; +} + +-(void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + // Make the application a foreground application (else it won't receive keyboard events) + ProcessSerialNumber psn = {0, kCurrentProcess}; + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + + // Menu + [self setupMenu]; + + NSOpenGLPixelFormatAttribute attrs[] = + { + NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFADepthSize, 32, + 0 + }; + + NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + AppView* view = [[AppView alloc] initWithFrame:self.window.frame pixelFormat:format]; + format = nil; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) + [view setWantsBestResolutionOpenGLSurface:YES]; +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + [self.window setContentView:view]; + + if ([view openGLContext] == nil) + NSLog(@"No OpenGL Context!"); + + [view initialize]; +} + +@end + +//----------------------------------------------------------------------------------- +// Application main() function +//----------------------------------------------------------------------------------- + +int main(int argc, const char* argv[]) +{ + @autoreleasepool + { + NSApp = [NSApplication sharedApplication]; + AppDelegate* delegate = [[AppDelegate alloc] init]; + [[NSApplication sharedApplication] setDelegate:delegate]; + [NSApp run]; + } + return NSApplicationMain(argc, argv); +} From 49fa47fc19ce650731a38a426a0da2f5f24317e3 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Thu, 25 Jun 2026 21:00:14 -0300 Subject: [PATCH 172/254] Added IM_FMTARGS/IM_FMTLIST to TextAligned() functions. (#1980, #9455) --- imgui_internal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index d837a6962..42c6f4d3b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3743,8 +3743,8 @@ namespace ImGui // Widgets: Text IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); - IMGUI_API void TextAligned(float align_x, float size_x, const char* fmt, ...); // FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) - IMGUI_API void TextAlignedV(float align_x, float size_x, const char* fmt, va_list args); + IMGUI_API void TextAligned(float align_x, float width, const char* fmt, ...) IM_FMTARGS(3); // FIXME-WIP: Works but API is likely to be reworked. + IMGUI_API void TextAlignedV(float align_x, float width, const char* fmt, va_list args) IM_FMTLIST(3); // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); From 5de45fb1d9811e31e393c2da90d6b432fd14c4b5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Aug 2026 15:42:27 +0200 Subject: [PATCH 173/254] Trim trailing white spaces. Changes some NULL to nullptr. --- docs/FAQ.md | 8 ++++---- docs/FONTS.md | 2 +- examples/example_glfw_wgpu/main.cpp | 2 +- examples/example_sdl3_wgpu/main.cpp | 12 ++++++------ imgui.h | 8 ++++---- imgui_draw.cpp | 2 +- imgui_internal.h | 4 ++-- imgui_tables.cpp | 2 +- misc/freetype/imgui_freetype.cpp | 4 ++-- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index d67616725..5e3bd1e83 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -570,8 +570,8 @@ typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or inte // Store a ImTextureID _or_ a ImTextureData*. struct ImTextureRef { - ImTextureRef() { _TexData = NULL; _TexID = ImTextureID_Invalid; } - ImTextureRef(ImTextureID tex_id) { _TexData = NULL; _TexID = tex_id; } + ImTextureRef() { _TexData = nullptr; _TexID = ImTextureID_Invalid; } + ImTextureRef(ImTextureID tex_id) { _TexData = nullptr; _TexID = tex_id; } inline ImTextureID GetTexID() const { return _TexData ? _TexData->TexID : _TexID; } // Members (either are set, never both!) @@ -589,7 +589,7 @@ struct ImTextureRef We intentionally do not provide an `ImTextureRef` constructor for this: we don't expect this to be frequently useful to the end-user, and it would be erroneously called by many legacy code. - There is no constructor to create a `ImTextureRef` from a `ImTextureData*` as we don't expect this to be useful to the end-user, and it would be erroneously called by many legacy code. - If you want to bind the current atlas when using custom rectangles, you can use `io.Fonts->TexRef`. - - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. `inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; }` + - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. `inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = nullptr, .TexID = tex_id }; return tex_ref; }` **Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward.** @@ -780,7 +780,7 @@ style.FontScaleDpi = 2.0f; To change font size: ```cpp -ImGui::PushFont(NULL, 42.0f); // This will be multiplied by style.FontScaleDpi +ImGui::PushFont(nullptr, 42.0f); // This will be multiplied by style.FontScaleDpi ``` To change font and font size: ```cpp diff --git a/docs/FONTS.md b/docs/FONTS.md index ac93bca0c..3f1e085fe 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -96,7 +96,7 @@ atlas->AddFont(...); v1.92 introduces a newer, dynamic font system. It requires backend to support the `ImGuiBackendFlags_HasTextures` feature: - Users of icons, Asian and non-English languages do not need to pre-build all glyphs ahead of time. Saving on loading time, memory, and also reducing issues with missing glyphs. Specifying glyph ranges is not needed anymore. -- `PushFont(NULL, new_size)` may be used anytime to change font size. +- `PushFont(nullptr, new_size)` may be used anytime to change font size. - Packing custom rectangles is more convenient as pixels may be written to immediately. - Any update to fonts previously required backend specific calls to re-upload the texture, and said calls were not portable across backends. It is now possible to scale fonts etc. in a way that doesn't require you to make backend-specific calls. - It is possible to plug a custom loader/backend to any font source. diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index 9d48258ba..dc6a7d155 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -548,7 +548,7 @@ WGPUSurface CreateWGPUSurface(const WGPUInstance& instance, GLFWwindow* window) { create_info.System = "win32"; create_info.RawWindow = (void*)glfwGetWin32Window(window); - create_info.RawInstance = (void*)::GetModuleHandle(NULL); + create_info.RawInstance = (void*)::GetModuleHandle(nullptr); return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); } #else diff --git a/examples/example_sdl3_wgpu/main.cpp b/examples/example_sdl3_wgpu/main.cpp index bc1787829..6b9a24e11 100644 --- a/examples/example_sdl3_wgpu/main.cpp +++ b/examples/example_sdl3_wgpu/main.cpp @@ -505,29 +505,29 @@ WGPUSurface CreateWGPUSurface(const WGPUInstance& instance, SDL_Window* window) #if defined(SDL_PLATFORM_MACOS) { create_info.System = "cocoa"; - create_info.RawWindow = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + create_info.RawWindow = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); } #elif defined(SDL_PLATFORM_LINUX) if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) { create_info.System = "wayland"; - create_info.RawDisplay = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); - create_info.RawSurface = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + create_info.RawDisplay = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr); + create_info.RawSurface = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); } else if (!SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11")) { create_info.System = "x11"; create_info.RawWindow = (void*)SDL_GetNumberProperty(propertiesID, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); - create_info.RawDisplay = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + create_info.RawDisplay = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr); return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); } #elif defined(SDL_PLATFORM_WIN32) { create_info.System = "win32"; - create_info.RawWindow = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); - create_info.RawInstance = (void*)::GetModuleHandle(NULL); + create_info.RawWindow = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); + create_info.RawInstance = (void*)::GetModuleHandle(nullptr); return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); } #else diff --git a/imgui.h b/imgui.h index 43f297d44..54eea3035 100644 --- a/imgui.h +++ b/imgui.h @@ -1100,7 +1100,7 @@ namespace ImGui // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. // - The return value of SetItemKeyOwner() says if ownership has been requested for the item, which is a shortcut to calling yet non-public TestKeyOwner() function. // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. - IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Return true when ownership has been set. Roughly equivalent to 'if (TestKeyOwner(key, GetItemID()) && (IsItemHovered() || IsItemActive())) { SetKeyOwner(key, GetItemID());'. // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. @@ -2356,7 +2356,7 @@ struct ImGuiStyle ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. - float MenuItemRounding; // Radius of MenuItem, BeginMenu rounding. + float MenuItemRounding; // Radius of MenuItem, BeginMenu rounding. float SelectableRounding; // Radius of Selectable rounding. MODIFYING THIS IS DISCOURAGED. CONTIGUOUS SELECTIONS WILL NOT LOOK RIGHT. (#7589) float DragDropTargetRounding; // Radius of the drag and drop target frame. When <0.0f: use FrameRounding. float DragDropTargetBorderSize; // Thickness of the drag and drop target border. @@ -3746,8 +3746,8 @@ struct ImFontAtlas IMGUI_API void CompactCache(); // Compact cached glyphs and texture. IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. - // Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts. - // - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards. + // Clearing the atlas/fonts has little use nowadays, unless you want to batch remove all fonts. + // - Since 1.92, you can call ClearFonts() mid-frame, if you load new fonts afterwards. // - As we are transitioning toward our new font system the semantic for those functions gets increasingly misleading and are often a source of issues. // TL;DR; most likely, don't use any of those functions. We expect to obsolete/rework them. IMGUI_API void Clear(); // Clear everything (fonts + textures). Don't call mid-frame! diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 35ac29f47..b4739b8be 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -105,7 +105,7 @@ namespace IMGUI_STB_NAMESPACE #pragma warning (push) #pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration #pragma warning (disable: 6011) // (stb_rectpack) Dereferencing NULL pointer 'cur->next'. -#pragma warning (disable: 5262) // (stb_truetype) implicit fall-through occurs here; are you missing a break statement? +#pragma warning (disable: 5262) // (stb_truetype) implicit fall-through occurs here; are you missing a break statement? #pragma warning (disable: 6385) // (stb_truetype) Reading invalid data from 'buffer': the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read. #pragma warning (disable: 28182) // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did. #endif diff --git a/imgui_internal.h b/imgui_internal.h index 42c6f4d3b..ef5bf9c4b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -284,7 +284,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 #define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! ImTrunc() is not inlined in MSVC debug builds -#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! +#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! //#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023) // Hint for branch prediction @@ -3253,7 +3253,7 @@ struct ImGuiTableSettings }; namespace ImGui -{ +{ // Tables: Candidates for public API IMGUI_API void TableOpenContextMenu(int column_n = -1); IMGUI_API void TableSetColumnWidth(int column_n, float width); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 8f260c873..bdaeeaad3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -631,7 +631,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn() if (table->ColumnsNames.Buf.Size > 0) table->ColumnsNames.Buf.resize(0); - + return true; } diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 3c817d20f..79dff8bfb 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -508,9 +508,9 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf // Load metrics only mode const float advance_x = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density; - if (out_advance_x != NULL) + if (out_advance_x != nullptr) { - IM_ASSERT(out_glyph == NULL); + IM_ASSERT(out_glyph == nullptr); *out_advance_x = advance_x; return true; } From 551a0a6bc052a585ebbe5b3ae7a64696048c04f2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Aug 2026 17:03:56 +0200 Subject: [PATCH 174/254] TextAligned, TextAlignedV: rework to always use provided width. (#7024, #9455, #1980) --- imgui.h | 2 +- imgui_widgets.cpp | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/imgui.h b/imgui.h index 54eea3035..07175b032 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.93.0 WIP" -#define IMGUI_VERSION_NUM 19293 +#define IMGUI_VERSION_NUM 19294 #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 5bd6da9fa..2fe200f5e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -342,18 +342,18 @@ void ImGui::TextWrappedV(const char* fmt, va_list args) PopTextWrapPos(); } -void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...) +void ImGui::TextAligned(float align_x, float width, const char* fmt, ...) { va_list args; va_start(args, fmt); - TextAlignedV(align_x, size_x, fmt, args); + TextAlignedV(align_x, width, fmt, args); va_end(args); } // align_x: 0.0f = left, 0.5f = center, 1.0f = right. -// size_x : 0.0f = shortcut for GetContentRegionAvail().x -// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) -void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args) +// width < 0.0f (e.g. -FLT_MIN): shortcut for GetContentRegionAvail().x +// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024, #9455) +void ImGui::TextAlignedV(float align_x, float width, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) @@ -362,23 +362,25 @@ void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list a const char* text, *text_end; ImFormatStringToTempBufferV(&text, &text_end, fmt, args); const ImVec2 text_size = CalcTextSize(text, text_end); - size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x; + const ImVec2 size = CalcItemSize(ImVec2(width, 0.0f), 0.0f, text_size.y); ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y); - ImVec2 size(ImMin(size_x, text_size.x), text_size.y); + ImVec2 pos_max(pos.x + size.x, window->ClipRect.Max.y); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x); window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x); - if (align_x > 0.0f && text_size.x < size_x) - pos.x += ImTrunc((size_x - text_size.x) * align_x); - RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size); - const ImVec2 backup_max_pos = window->DC.CursorMaxPos; ItemSize(size); - ItemAdd(ImRect(pos, pos + size), 0); - window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up. + const bool is_visible = ItemAdd(ImRect(pos, pos + size), 0); + window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content to 'size' to use 'text_size' allowing auto-resize to function with align_x > 0.0f + if (!is_visible) + return; - if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip)) + if (align_x > 0.0f && text_size.x < size.x) + pos.x += ImTrunc((size.x - text_size.x) * align_x); + RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size); + + if (size.x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip)) SetTooltip("%.*s", (int)(text_end - text), text); } From 46d39d56febc2a00bdd2270dc88c8a13f2a0441a Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 7 Aug 2026 18:42:39 +0200 Subject: [PATCH 175/254] Fixed GCC false positive. (#9508) --- imgui_widgets.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2fe200f5e..062e26d73 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3635,6 +3635,7 @@ const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_ const char* fmt_end = ImParseFormatFindEnd(fmt_start); if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. return fmt_start; + buf[0] = 0; // Fix false positive with caller assuming that buf could be non-initialized (#9508) ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size)); return buf; } From 3e9d0f7e8227d0eaa64b8ea9771073e7249292fb Mon Sep 17 00:00:00 2001 From: rubbededo Date: Sun, 16 Aug 2026 01:40:58 +0000 Subject: [PATCH 176/254] Backends: WebGPU: fixed passing non-integer sizes to wgpuRenderPassEncoderSetViewport(). (#9515, #8628) --- backends/imgui_impl_wgpu.cpp | 5 ++++- docs/CHANGELOG.txt | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index ce5974b41..43a7fbba7 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-08-18: Fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport() when FramebufferScale is >1.0f. (#9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-03-25: Added support for WGVK native backend via IMGUI_IMPL_WEBGPU_BACKEND_WGVK define, with SPIRV shaders if WGSL is not available. (#9316, #9246, #9257) // 2026-03-09: Removed support for Emscripten < 4.0.10. (#9281) @@ -440,7 +441,9 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas } // Setup viewport - wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->FramebufferScale.x * draw_data->DisplaySize.x, draw_data->FramebufferScale.y * draw_data->DisplaySize.y, 0, 1); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + wgpuRenderPassEncoderSetViewport(ctx, 0, 0, fb_width, fb_height, 0, 1); // Bind shader and vertex buffers wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, 0, fr->VertexBufferSize * sizeof(ImDrawVert)); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 716cd3fc7..14978c699 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,8 @@ Other Changes: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without going through a SDL_Renderer. (#5592) [@lailoken] + - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` + when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - Examples: - SDL2+Metal: create Metal context without using a SDL_Renderer. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] From 1d7b37dc9b825a8aa49412e7f9a5546be0006ce4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 19 Aug 2026 13:27:31 +0200 Subject: [PATCH 177/254] Tables: fix/workaround for losing display order. (#9519, #9108) --- imgui_tables.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index bdaeeaad3..fb1b998d0 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3974,6 +3974,8 @@ void ImGui::TableSaveSettings(ImGuiTable* table) if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; } + if (table->Flags & ImGuiTableFlags_Sortable) + settings->SaveFlags |= ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable; settings->SaveFlags &= table->Flags; settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; From 0af8f475d1cf8b98d603702059c0c73deeb81ac2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 23 Aug 2026 13:05:33 +0200 Subject: [PATCH 178/254] Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large. (#9521) Using a FrameCount for compare. Kept time as float to be consistent with other GC-related timestamps: if there is an actual need to use double we'll switch everywhere. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 7 ++++--- imgui_internal.h | 5 +++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 14978c699..21f452486 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -56,6 +56,9 @@ Other Changes: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of ProggyClean on most Apple/Retina setups by default. +- Misc: + - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset + every frame when cumulated session time is very large (e.g. a few days). [@lailoken] - ImDrawList: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. diff --git a/imgui.cpp b/imgui.cpp index d157353cf..c4ac84770 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5305,12 +5305,13 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw } // Our ImDrawList system requires that there is always a command - if (viewport->BgFgDrawListsLastTimeActive[drawlist_no] != (float)g.Time) + if (viewport->BgFgDrawListsLastFrameActive[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); draw_list->_SetPixelDensity(viewport->FramebufferScale.x); draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); + viewport->BgFgDrawListsLastFrameActive[drawlist_no] = g.FrameCount; viewport->BgFgDrawListsLastTimeActive[drawlist_no] = (float)g.Time; } return draw_list; @@ -6212,7 +6213,7 @@ void ImGui::Render() for (ImGuiViewportP* viewport : g.Viewports) { InitViewportDrawData(viewport); - if (viewport->BgFgDrawLists[0] != NULL && viewport->BgFgDrawListsLastTimeActive[0] == (float)g.Time) + if (viewport->BgFgDrawLists[0] != NULL && viewport->BgFgDrawListsLastFrameActive[0] == g.FrameCount) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); } @@ -6244,7 +6245,7 @@ void ImGui::Render() FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder); // Add foreground ImDrawList (for each active viewport) - if (viewport->BgFgDrawLists[1] != NULL && viewport->BgFgDrawListsLastTimeActive[1] == (float)g.Time) + if (viewport->BgFgDrawLists[1] != NULL && viewport->BgFgDrawListsLastFrameActive[1] == g.FrameCount) 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). diff --git a/imgui_internal.h b/imgui_internal.h index ef5bf9c4b..631216426 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1999,8 +1999,9 @@ struct IMGUI_API ImGuiMultiSelectState // Every instance of ImGuiViewport is in fact a ImGuiViewportP. struct ImGuiViewportP : public ImGuiViewport { - 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. + int BgFgDrawListsLastFrameActive[2]; // Last frame the background (0) and foreground (1) draw lists were used. + float BgFgDrawListsLastTimeActive[2]; // Timestamps for Gc. ImDrawData DrawDataP; ImDrawDataBuilder DrawDataBuilder; // Temporary data while building final ImDrawData @@ -2013,7 +2014,7 @@ struct ImGuiViewportP : public ImGuiViewport ImVec2 BuildWorkInsetMin; // Work Area inset accumulator for current frame, to become next frame's WorkInset ImVec2 BuildWorkInsetMax; // " - ImGuiViewportP() { BgFgDrawListsLastTimeActive[0] = BgFgDrawListsLastTimeActive[1] = -1.0f; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; } + ImGuiViewportP() { BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; BgFgDrawListsLastFrameActive[0] = BgFgDrawListsLastFrameActive[1] = -1; BgFgDrawListsLastTimeActive[0] = BgFgDrawListsLastTimeActive[1] = -1.0; } ~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 1e51b6ffdc24f06a6e3cd3adaa7fc154af701d2f Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 28 Aug 2026 12:40:14 +0200 Subject: [PATCH 179/254] Comments/fixes about using IMGUI_DEFINE_MATH_OPERATORS in imconfig.h (#9524) --- imconfig.h | 5 ++++- misc/single_file/imgui_single_file.h | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/imconfig.h b/imconfig.h index b40db3898..fd1ed81f3 100644 --- a/imconfig.h +++ b/imconfig.h @@ -114,8 +114,11 @@ constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ operator MyVec4() const { return MyVec4(x,y,z,w); } */ -//---- ...Or use Dear ImGui's own very basic math operators. +//---- ...Or use Dear ImGui's own basic math operators in every file including imgui.h: +//#ifndef IMGUI_DEFINE_MATH_OPERATORS //#define IMGUI_DEFINE_MATH_OPERATORS +//#endif +//---- ...Or manually '#define IMGUI_DEFINE_MATH_OPERATORS' before '#include "imgui.h"' to use from one file. //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). diff --git a/misc/single_file/imgui_single_file.h b/misc/single_file/imgui_single_file.h index 7ca31e0fe..bea1cb6b6 100644 --- a/misc/single_file/imgui_single_file.h +++ b/misc/single_file/imgui_single_file.h @@ -1,14 +1,14 @@ // dear imgui: single-file wrapper include // We use this to validate compiling all *.cpp files in a same compilation unit. -// Users of that technique (also called "Unity builds") can generally provide this themselves, -// so we don't really recommend you use this in your projects. +// Users of that technique (also called "Unity builds") can generally provide this themselves easily. // Do this: // #define IMGUI_IMPLEMENTATION -// Before you include this file in *one* C++ file to create the implementation. +// #include "imgui_single_file.h" +// In *one* C++ file to create the implementation. // Using this in your project will leak the contents of imgui_internal.h and ImVec2 operators in this compilation unit. -#ifdef IMGUI_IMPLEMENTATION +#if defined(IMGUI_IMPLEMENTATION) && !defined(IMGUI_DEFINE_MATH_OPERATORS) #define IMGUI_DEFINE_MATH_OPERATORS #endif From d3c5ff395ca10735456331c993e0789c2ccec355 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 30 Aug 2026 19:44:15 +0200 Subject: [PATCH 180/254] Drags, Sliders: fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types. (#9526, #3361, #1823, #1316, #642) --- docs/CHANGELOG.txt | 3 +++ imgui_widgets.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 21f452486..2e010adeb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -52,6 +52,9 @@ Breaking Changes: Other Changes: +- Drags, Sliders: + - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which + leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] - Fonts: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 062e26d73..e2a6771bd 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3006,7 +3006,7 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T result = 0.0f; // Workaround for values that are in-range but below our fudge else if (v_clamped >= v_max_fudged) result = 1.0f; // Workaround for values that are in-range but above our fudge - else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions + else if (((FLOATTYPE)v_min * (FLOATTYPE)v_max) < 0.0f) // Range crosses zero, so split into two portions { float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; @@ -3060,7 +3060,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range - if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts + if (((FLOATTYPE)v_min * (FLOATTYPE)v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts { float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; From 760dff324402f4faa00fb85c346fbf5780e97cec Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 11:32:24 +0200 Subject: [PATCH 181/254] Docs: misc additions. --- .github/ISSUE_TEMPLATE/issue_template.yml | 1 + .github/pull_request_template.md | 2 +- docs/CONTRIBUTING.md | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue_template.yml b/.github/ISSUE_TEMPLATE/issue_template.yml index 6ed62493a..dda2213c3 100644 --- a/.github/ISSUE_TEMPLATE/issue_template.yml +++ b/.github/ISSUE_TEMPLATE/issue_template.yml @@ -14,6 +14,7 @@ body: attributes: value: | **Prerequisites:** + - I am not using a LLM to think or write for me. Please distill information to what's necessary and meaningful. - I have read [Frequently Asked Questions](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md). - I have read [Contributing Guidelines -> General Advices](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#getting-started--general-advice). - I have read [Contributing Guidelines -> How to open an Issue](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#how-to-open-an-issue). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 27cb3e1dd..8061997cf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,7 +6,7 @@ 3. Consider running the [imgui_test_suite](https://github.com/ocornut/imgui_test_engine) or adding new tests to test for expected behaviors. -4. PLEASE DO NOT SUBMIT AI SLOP OR AUTOMATED REPORTS. Submissions that are lacking proper human review/judgement before being passed to us are not welcome. +4. PLEASE DO NOT SUBMIT AI SLOP OR AUTOMATED AI REPORTS. Submissions that are lacking proper human review/judgement before being passed to us are not welcome. Do not let a LLM AI write most of the text for you. 5. Clear this template before submitting your PR. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fb946c4b3..699209f3a 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -3,7 +3,7 @@ ## Index - [Getting Started & General Advice](#getting-started--general-advice) -- [Issues vs Discussions](#issues-vs-discussions) +- [Using "Issues" for almost everything!](using-issues-for-almost-everything) - [How to open an Issue](#how-to-open-an-issue) - [How to open a Pull Request](#how-to-open-a-pull-request) - [Copyright / Contributor License Agreement](#copyright--contributor-license-agreement) @@ -23,18 +23,20 @@ - If you get a crash or assert, use a debugger to locate the line triggering it and read the comments around. - Please don't be a [Help Vampire](https://slash7.com/2006/12/22/vampires/). -## 'Issues' vs 'Discussions' +## Using "Issues" for almost everything! -We are happy to use 'Issues' for many type of open-ended questions. We are encouraging 'Issues' becoming an enormous, centralized and cross-referenced database of Dear ImGui contents. +We are happy to use 'Issues' for many type of open-ended questions. +We are encouraging 'Issues' becoming an enormous, centralized and cross-referenced database of Dear ImGui contents. -Only if you: +- Need help using the API? Open an Issue! +- Want to suggest a feature? Open an Issue! +- etc. + +ONLY IN THOSE CASE you can use the [Discussions forums](https://github.com/ocornut/imgui/discussions) - Cannot BUILD or LINK examples. - Cannot BUILD, or LINK, or RUN Dear ImGui in your application or custom engine. -- Cannot LOAD a font. -Then please [use the Discussions forums](https://github.com/ocornut/imgui/discussions) instead of opening an issue. - -If Dear ImGui is successfully showing in your app and you have used Dear ImGui before, you can open an Issue. Any form of discussions is welcome as a new issue. +If Dear ImGui is successfully showing in your app and you have used Dear ImGui before, you can open an Issue. Any form of discussions is welcome as a new Issue. ## How to open an issue From 6d1f88fe4a9fb63ad78f8dbf80bc6641355cd2ed Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 11:56:01 +0200 Subject: [PATCH 182/254] Backends: WebGPU: update to build with wgpu-native 29.0+. (#9532, #9530) --- backends/imgui_impl_wgpu.cpp | 21 ++++++++++++++------- docs/CHANGELOG.txt | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 43a7fbba7..0a11cc1f9 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-08-31: Update to compile with wgpu-native 29.0. (#9532, #9530) // 2026-08-18: Fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport() when FramebufferScale is >1.0f. (#9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-03-25: Added support for WGVK native backend via IMGUI_IMPL_WEBGPU_BACKEND_WGVK define, with SPIRV shaders if WGSL is not available. (#9316, #9246, #9257) @@ -66,12 +67,24 @@ #error Emscripten <4.0.10 with '-sUSE_WEBGPU=1' is not supported anymore. #endif -#if defined IMGUI_IMPL_WEBGPU_BACKEND_DAWN || defined IMGUI_IMPL_WEBGPU_BACKEND_WGVK +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) // Dawn renamed WGPUProgrammableStageDescriptor to WGPUComputeState (see: https://github.com/webgpu-native/webgpu-headers/pull/413) // Using type alias until WGPU adopts the same naming convention (#8369) using WGPUProgrammableStageDescriptor = WGPUComputeState; #endif +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#ifndef WGPUProgrammableStageDescriptor +#define WGPUProgrammableStageDescriptor WGPUFragmentState +#endif +#ifndef WGPUSurfaceGetCurrentTextureStatus_OutOfMemory +#define WGPUSurfaceGetCurrentTextureStatus_OutOfMemory WGPUSurfaceGetCurrentTextureStatus_Outdated +#endif +#ifndef WGPUSurfaceGetCurrentTextureStatus_DeviceLost +#define WGPUSurfaceGetCurrentTextureStatus_DeviceLost WGPUSurfaceGetCurrentTextureStatus_Timeout +#endif +#endif + // Dear ImGui prototypes from imgui_internal.h extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed); #define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro). @@ -780,15 +793,9 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() // Vertex input configuration WGPUVertexAttribute attribute_desc[] = { -#if defined IMGUI_IMPL_WEBGPU_BACKEND_DAWN || defined IMGUI_IMPL_WEBGPU_BACKEND_WGVK { nullptr, WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, pos), 0 }, { nullptr, WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, uv), 1 }, { nullptr, WGPUVertexFormat_Unorm8x4, (uint64_t)offsetof(ImDrawVert, col), 2 }, -#else - { WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, pos), 0 }, - { WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, uv), 1 }, - { WGPUVertexFormat_Unorm8x4, (uint64_t)offsetof(ImDrawVert, col), 2 }, -#endif }; WGPUVertexBufferLayout buffer_layouts[1]; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2e010adeb..88fc4b5fe 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -72,6 +72,7 @@ Other Changes: going through a SDL_Renderer. (#5592) [@lailoken] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] + - WebGPU: update to build with wgpu-native 29.0+. (#9532, #9530) [@ivan-enzhaev] - Examples: - SDL2+Metal: create Metal context without using a SDL_Renderer. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] From e44b1129bdb0a86107b37f0060a900d8582ccb63 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 12:09:03 +0200 Subject: [PATCH 183/254] Backends, Examples: WebGPU: fixed warnings, renamed .sln, fixed SDL2+WGPU cmakefile. --- backends/imgui_impl_wgpu.cpp | 2 +- examples/example_glfw_wgpu/CMakeLists.txt | 18 +++++++------- examples/example_glfw_wgpu/main.cpp | 29 ++++++++++------------- examples/example_sdl2_wgpu/CMakeLists.txt | 20 ++++++++-------- examples/example_sdl2_wgpu/main.cpp | 23 ++++++++---------- examples/example_sdl3_wgpu/CMakeLists.txt | 2 +- examples/example_sdl3_wgpu/main.cpp | 23 ++++++++---------- 7 files changed, 54 insertions(+), 63 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 0a11cc1f9..745ce3623 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -456,7 +456,7 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas // Setup viewport int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); - wgpuRenderPassEncoderSetViewport(ctx, 0, 0, fb_width, fb_height, 0, 1); + wgpuRenderPassEncoderSetViewport(ctx, 0, 0, (float)fb_width, (float)fb_height, 0, 1); // Bind shader and vertex buffers wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, 0, fr->VertexBufferSize * sizeof(ImDrawVert)); diff --git a/examples/example_glfw_wgpu/CMakeLists.txt b/examples/example_glfw_wgpu/CMakeLists.txt index 4eaaee762..b76054877 100644 --- a/examples/example_glfw_wgpu/CMakeLists.txt +++ b/examples/example_glfw_wgpu/CMakeLists.txt @@ -1,10 +1,3 @@ -# Building for desktop with Dawn: -# 1. git clone https://github.com/google/dawn dawn -# 2. cmake -B build -DIMGUI_DAWN_DIR=dawn -# 3. cmake --build build -# The resulting binary will be found at one of the following locations: -# * build/example_glfw_wgpu[.exe] or build/Debug/example_glfw_wgpu[.exe] - # Building for desktop with WGPU-Native: # 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases # 2. unzip the downloaded file in your_preferred_folder @@ -13,7 +6,14 @@ # The resulting binary will be found at one of the following locations: # * build/example_glfw_wgpu[.exe] or build/Debug/example_glfw_wgpu[.exe] -# Building for desktop with WGVK (MUCH EASIER) +# Building for desktop with Dawn: (VERY LONG TO BUILD) +# 1. git clone https://github.com/google/dawn dawn +# 2. cmake -B build -DIMGUI_DAWN_DIR=dawn +# 3. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/example_glfw_wgpu[.exe] or build/Debug/example_glfw_wgpu[.exe] + +# Building for desktop with WGVK: # 1. git clone https://github.com/manuel5975p/WGVK wgvk # 2. cmake -B build -DIMGUI_WGVK_DIR=wgvk # 3. cmake --build build @@ -29,7 +29,7 @@ # 4. emrun build/index.html cmake_minimum_required(VERSION 3.22) # Dawn requires CMake >= 3.22 -project(imgui_example_glfw_wgpu C CXX) +project(example_glfw_wgpu C CXX) set(IMGUI_EXECUTABLE example_glfw_wgpu) diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index dc6a7d155..1a00751cc 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -344,29 +344,25 @@ static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter static void handle_request_adapter(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void* userdata1, void* userdata2) { IM_UNUSED(userdata2); - if (status == WGPURequestAdapterStatus_Success) + if (status != WGPURequestAdapterStatus_Success) { - WGPUAdapter* extAdapter = (WGPUAdapter*)userdata1; - *extAdapter = adapter; - } - else - { - printf("Request_adapter status=%#.8x message=%.*s\n", status, (int) message.length, message.data); + printf("Request_adapter status=%#.8x message=%.*s\n", status, (int)message.length, message.data); + return; } + WGPUAdapter* extAdapter = (WGPUAdapter*)userdata1; + *extAdapter = adapter; } static void handle_request_device(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void* userdata1, void* userdata2) { IM_UNUSED(userdata2); - if (status == WGPURequestDeviceStatus_Success) + if (status != WGPURequestDeviceStatus_Success) { - WGPUDevice* extDevice = (WGPUDevice*)userdata1; - *extDevice = device; - } - else - { - printf("Request_device status=%#.8x message=%.*s\n", status, (int) message.length, message.data); + printf("Request_device status=%#.8x message=%.*s\n", status, (int)message.length, message.data); + return; } + WGPUDevice* extDevice = (WGPUDevice*)userdata1; + *extDevice = device; } static WGPUAdapter RequestAdapter(WGPUInstance& instance) @@ -393,6 +389,7 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) deviceCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; deviceCallbackInfo.callback = handle_request_device; deviceCallbackInfo.userdata1 = &local_device; + WGPUFuture future = wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); @@ -401,7 +398,7 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) } #endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU -bool InitWGPU(GLFWwindow* window) +static bool InitWGPU(GLFWwindow* window) { WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities @@ -450,7 +447,7 @@ bool InitWGPU(GLFWwindow* window) #if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) wgpuSetLogCallback( - [](WGPULogLevel level, WGPUStringView msg, void* userdata) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr + [](WGPULogLevel level, WGPUStringView msg, void*) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr ); wgpuSetLogLevel(WGPULogLevel_Warn); #endif diff --git a/examples/example_sdl2_wgpu/CMakeLists.txt b/examples/example_sdl2_wgpu/CMakeLists.txt index aff4a184f..0a8fed305 100644 --- a/examples/example_sdl2_wgpu/CMakeLists.txt +++ b/examples/example_sdl2_wgpu/CMakeLists.txt @@ -1,10 +1,3 @@ -# Building for desktop with Dawn: -# 1. git clone https://github.com/google/dawn dawn -# 2. cmake -B build -DIMGUI_DAWN_DIR=dawn -# 3. cmake --build build -# The resulting binary will be found at one of the following locations: -# * build/example_sdl2_wgpu[.exe] or build/Debug/example_sdl2_wgpu[.exe] - # Building for desktop with WGPU-Native: # 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases # 2. unzip the downloaded file in your_preferred_folder @@ -13,7 +6,14 @@ # The resulting binary will be found at one of the following locations: # * build/example_sdl2_wgpu[.exe] or build/Debug/example_sdl2_wgpu[.exe] -# Building for desktop with WGVK (MUCH EASIER) +# Building for desktop with Dawn: (VERY LONG TO BUILD) +# 1. git clone https://github.com/google/dawn dawn +# 2. cmake -B build -DIMGUI_DAWN_DIR=dawn +# 3. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/example_sdl2_wgpu[.exe] or build/Debug/example_sdl2_wgpu[.exe] + +# Building for desktop with WGVK: # 1. git clone https://github.com/manuel5975p/WGVK wgvk # 2. cmake -B build -DIMGUI_WGVK_DIR=wgvk # 3. cmake --build build @@ -29,7 +29,7 @@ # 4. emrun build/index.html cmake_minimum_required(VERSION 3.22) # Dawn requires CMake >= 3.22 -project(imgui_example_sdl2_wgpu C CXX) +project(example_sdl2_wgpu C CXX) set(IMGUI_EXECUTABLE example_sdl2_wgpu) @@ -37,7 +37,7 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() -set(CMAKE_CXX_STANDARD 10) # Dawn requires C++20 +set(CMAKE_CXX_STANDARD 20) # Dawn requires C++20 # Dear ImGui set(IMGUI_DIR ../../) diff --git a/examples/example_sdl2_wgpu/main.cpp b/examples/example_sdl2_wgpu/main.cpp index 6898d03cd..6caeecfa7 100644 --- a/examples/example_sdl2_wgpu/main.cpp +++ b/examples/example_sdl2_wgpu/main.cpp @@ -330,29 +330,25 @@ static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter static void handle_request_adapter(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void* userdata1, void* userdata2) { IM_UNUSED(userdata2); - if (status == WGPURequestAdapterStatus_Success) - { - WGPUAdapter* extAdapter = (WGPUAdapter*)userdata1; - *extAdapter = adapter; - } - else + if (status != WGPURequestAdapterStatus_Success) { printf("Request_adapter status=%#.8x message=%.*s\n", status, (int)message.length, message.data); + return; } + WGPUAdapter* extAdapter = (WGPUAdapter*)userdata1; + *extAdapter = adapter; } static void handle_request_device(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void* userdata1, void* userdata2) { IM_UNUSED(userdata2); - if (status == WGPURequestDeviceStatus_Success) - { - WGPUDevice* extDevice = (WGPUDevice*)userdata1; - *extDevice = device; - } - else + if (status != WGPURequestDeviceStatus_Success) { printf("Request_device status=%#.8x message=%.*s\n", status, (int)message.length, message.data); + return; } + WGPUDevice* extDevice = (WGPUDevice*)userdata1; + *extDevice = device; } static WGPUAdapter RequestAdapter(WGPUInstance& instance) @@ -379,6 +375,7 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) deviceCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; deviceCallbackInfo.callback = handle_request_device; deviceCallbackInfo.userdata1 = &local_device; + WGPUFuture future = wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); @@ -437,7 +434,7 @@ static bool InitWGPU(SDL_Window* window) #if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) wgpuSetLogCallback( - [](WGPULogLevel level, WGPUStringView msg, void* userdata) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr + [](WGPULogLevel level, WGPUStringView msg, void*) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr ); wgpuSetLogLevel(WGPULogLevel_Warn); #endif diff --git a/examples/example_sdl3_wgpu/CMakeLists.txt b/examples/example_sdl3_wgpu/CMakeLists.txt index b7b05881d..b05dcd459 100644 --- a/examples/example_sdl3_wgpu/CMakeLists.txt +++ b/examples/example_sdl3_wgpu/CMakeLists.txt @@ -29,7 +29,7 @@ # 5. emrun build/index.html cmake_minimum_required(VERSION 3.22) # Dawn requires CMake >= 3.22 -project(imgui_example_sdl3_wgpu C CXX) +project(example_sdl3_wgpu C CXX) set(IMGUI_EXECUTABLE example_sdl3_wgpu) diff --git a/examples/example_sdl3_wgpu/main.cpp b/examples/example_sdl3_wgpu/main.cpp index 6b9a24e11..393c7f44f 100644 --- a/examples/example_sdl3_wgpu/main.cpp +++ b/examples/example_sdl3_wgpu/main.cpp @@ -341,29 +341,25 @@ static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter static void handle_request_adapter(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void* userdata1, void* userdata2) { IM_UNUSED(userdata2); - if (status == WGPURequestAdapterStatus_Success) - { - WGPUAdapter* extAdapter = (WGPUAdapter*)userdata1; - *extAdapter = adapter; - } - else + if (status != WGPURequestAdapterStatus_Success) { printf("Request_adapter status=%#.8x message=%.*s\n", status, (int)message.length, message.data); + return; } + WGPUAdapter* extAdapter = (WGPUAdapter*)userdata1; + *extAdapter = adapter; } static void handle_request_device(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void* userdata1, void* userdata2) { IM_UNUSED(userdata2); - if (status == WGPURequestDeviceStatus_Success) - { - WGPUDevice* extDevice = (WGPUDevice*)userdata1; - *extDevice = device; - } - else + if (status != WGPURequestDeviceStatus_Success) { printf("Request_device status=%#.8x message=%.*s\n", status, (int)message.length, message.data); + return; } + WGPUDevice* extDevice = (WGPUDevice*)userdata1; + *extDevice = device; } static WGPUAdapter RequestAdapter(WGPUInstance& instance) @@ -390,6 +386,7 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) deviceCallbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; deviceCallbackInfo.callback = handle_request_device; deviceCallbackInfo.userdata1 = &local_device; + WGPUFuture future = wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); @@ -448,7 +445,7 @@ static bool InitWGPU(SDL_Window* window) #if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) wgpuSetLogCallback( - [](WGPULogLevel level, WGPUStringView msg, void* userdata) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr + [](WGPULogLevel level, WGPUStringView msg, void*) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr ); wgpuSetLogLevel(WGPULogLevel_Warn); #endif From 35a13e994878a7bfdc96c5c1faed843b282371e0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 12:19:14 +0200 Subject: [PATCH 184/254] Backends, Examples: WebGPU: further simplications. (#9377, #9532, #9530) Suggested by @lucascompython --- backends/imgui_impl_wgpu.cpp | 39 ++++++++---------------------------- backends/imgui_impl_wgpu.h | 3 +-- docs/CHANGELOG.txt | 2 +- 3 files changed, 10 insertions(+), 34 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 745ce3623..cf8f7a969 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -20,7 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-08-31: Update to compile with wgpu-native 29.0. (#9532, #9530) +// 2026-08-31: Update to compile with wgpu-native 29.0. (#9377, #9532, #9530) // 2026-08-18: Fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport() when FramebufferScale is >1.0f. (#9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-03-25: Added support for WGVK native backend via IMGUI_IMPL_WEBGPU_BACKEND_WGVK define, with SPIRV shaders if WGSL is not available. (#9316, #9246, #9257) @@ -67,24 +67,6 @@ #error Emscripten <4.0.10 with '-sUSE_WEBGPU=1' is not supported anymore. #endif -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) -// Dawn renamed WGPUProgrammableStageDescriptor to WGPUComputeState (see: https://github.com/webgpu-native/webgpu-headers/pull/413) -// Using type alias until WGPU adopts the same naming convention (#8369) -using WGPUProgrammableStageDescriptor = WGPUComputeState; -#endif - -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) -#ifndef WGPUProgrammableStageDescriptor -#define WGPUProgrammableStageDescriptor WGPUFragmentState -#endif -#ifndef WGPUSurfaceGetCurrentTextureStatus_OutOfMemory -#define WGPUSurfaceGetCurrentTextureStatus_OutOfMemory WGPUSurfaceGetCurrentTextureStatus_Outdated -#endif -#ifndef WGPUSurfaceGetCurrentTextureStatus_DeviceLost -#define WGPUSurfaceGetCurrentTextureStatus_DeviceLost WGPUSurfaceGetCurrentTextureStatus_Timeout -#endif -#endif - // Dear ImGui prototypes from imgui_internal.h extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed); #define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro). @@ -330,7 +312,7 @@ static void SafeRelease(FrameResources& res) SafeRelease(res.VertexBufferHost); } -static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModuleWGSL(const char* wgsl_source) +static WGPUComputeState ImGui_ImplWGPU_CreateShaderModuleWGSL(const char* wgsl_source) { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); IM_UNUSED(bd); @@ -357,7 +339,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModuleWGSL(con pop_cb.userdata1 = &validation_error; wgpuDevicePopErrorScope(bd->wgpuDevice, pop_cb); - WGPUProgrammableStageDescriptor stage_desc = {}; + WGPUComputeState stage_desc = {}; if (module && !validation_error) { stage_desc.module = module; @@ -370,7 +352,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModuleWGSL(con return stage_desc; } -static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModuleSPIRV(const void* spirv_binary, size_t spirv_length) +static WGPUComputeState ImGui_ImplWGPU_CreateShaderModuleSPIRV(const void* spirv_binary, size_t spirv_length) { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); @@ -382,7 +364,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModuleSPIRV(co WGPUShaderModuleDescriptor desc = {}; desc.nextInChain = (WGPUChainedStruct*)&spirv_desc; - WGPUProgrammableStageDescriptor stage_desc = {}; + WGPUComputeState stage_desc = {}; stage_desc.module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); stage_desc.entryPoint = { "main", WGPU_STRLEN }; @@ -785,7 +767,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.layout = wgpuDeviceCreatePipelineLayout(bd->wgpuDevice, &layout_desc); // Create the vertex shader - WGPUProgrammableStageDescriptor vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModuleWGSL(__shader_vert_wgsl); + WGPUComputeState vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModuleWGSL(__shader_vert_wgsl); if (!vertex_shader_desc.module) vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModuleSPIRV(__shader_vert_spirv, sizeof(__shader_vert_spirv)); graphics_pipeline_desc.vertex.module = vertex_shader_desc.module; graphics_pipeline_desc.vertex.entryPoint = vertex_shader_desc.entryPoint; @@ -808,7 +790,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.vertex.buffers = buffer_layouts; // Create the pixel shader - WGPUProgrammableStageDescriptor pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModuleWGSL(__shader_frag_wgsl); + WGPUComputeState pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModuleWGSL(__shader_frag_wgsl); if (!pixel_shader_desc.module) pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModuleSPIRV(__shader_frag_spirv, sizeof(__shader_frag_spirv)); // Create the blending setup @@ -1001,11 +983,7 @@ void ImGui_ImplWGPU_NewFrame() bool ImGui_ImplWGPU_IsSurfaceStatusError(WGPUSurfaceGetCurrentTextureStatus status) { -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) return (status == WGPUSurfaceGetCurrentTextureStatus_Error); -#else - return (status == WGPUSurfaceGetCurrentTextureStatus_OutOfMemory || status == WGPUSurfaceGetCurrentTextureStatus_DeviceLost); -#endif } bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextureStatus status) @@ -1018,7 +996,6 @@ bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextureStatus } // Helpers to obtain a string -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) const char* ImGui_ImplWGPU_GetErrorTypeName(WGPUErrorType type) { switch (type) @@ -1041,7 +1018,7 @@ const char* ImGui_ImplWGPU_GetDeviceLostReasonName(WGPUDeviceLostReason type) default: return "Unknown"; } } -#elif !defined(__EMSCRIPTEN__) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) const char* ImGui_ImplWGPU_GetLogLevelName(WGPULogLevel level) { switch (level) diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index c06c25f8a..ed84f5ecb 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -95,10 +95,9 @@ bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextur void ImGui_ImplWGPU_DebugPrintAdapterInfo(const WGPUAdapter& adapter); const char* ImGui_ImplWGPU_GetBackendTypeName(WGPUBackendType type); const char* ImGui_ImplWGPU_GetAdapterTypeName(WGPUAdapterType type); -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) const char* ImGui_ImplWGPU_GetDeviceLostReasonName(WGPUDeviceLostReason type); const char* ImGui_ImplWGPU_GetErrorTypeName(WGPUErrorType type); -#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) const char* ImGui_ImplWGPU_GetLogLevelName(WGPULogLevel level); #endif diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 88fc4b5fe..e732f5b0a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -72,7 +72,7 @@ Other Changes: going through a SDL_Renderer. (#5592) [@lailoken] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - - WebGPU: update to build with wgpu-native 29.0+. (#9532, #9530) [@ivan-enzhaev] + - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] - Examples: - SDL2+Metal: create Metal context without using a SDL_Renderer. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] From 7e0de0e411f8ac43ab19603ce64994015f1f12c3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 12:30:37 +0200 Subject: [PATCH 185/254] Examples: GLFW/SDL2/SDL3+WebGPU: fixed running with wgpu-native missing wgpuInstanceWaitAny(). (#9377) --- docs/CHANGELOG.txt | 3 ++- examples/example_glfw_wgpu/main.cpp | 6 ++++++ examples/example_sdl2_wgpu/main.cpp | 6 ++++++ examples/example_sdl3_wgpu/main.cpp | 6 ++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e732f5b0a..492b9eff8 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -74,7 +74,8 @@ Other Changes: when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] - Examples: - - SDL2+Metal: create Metal context without using a SDL_Renderer. + - GLFW/SDL2/SDL3+WebGPU: fixed running with wgpu-native missing `wgpuInstanceWaitAny()`. (#9377) + - SDL2+Metal: create Metal context without using a `SDL_Renderer`. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] - OSX+OpenGL3: added example_apple_opengl3/ (OSX/Cocoa + OpenGL 3.2 Core profile). diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index 1a00751cc..fe8b3de49 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -376,8 +376,10 @@ static WGPUAdapter RequestAdapter(WGPUInstance& instance) adapterCallbackInfo.userdata1 = &local_adapter; WGPUFuture future = wgpuInstanceRequestAdapter(instance, &adapter_options, adapterCallbackInfo); +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); +#endif IM_ASSERT(local_adapter && "Error on Adapter request"); return local_adapter; } @@ -391,8 +393,12 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) deviceCallbackInfo.userdata1 = &local_device; WGPUFuture future = wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); +#else + IM_UNUSED(instance); +#endif IM_ASSERT(local_device && "Error on Device request"); return local_device; } diff --git a/examples/example_sdl2_wgpu/main.cpp b/examples/example_sdl2_wgpu/main.cpp index 6caeecfa7..b0fa54179 100644 --- a/examples/example_sdl2_wgpu/main.cpp +++ b/examples/example_sdl2_wgpu/main.cpp @@ -362,8 +362,10 @@ static WGPUAdapter RequestAdapter(WGPUInstance& instance) adapterCallbackInfo.userdata1 = &local_adapter; WGPUFuture future = wgpuInstanceRequestAdapter(instance, &adapter_options, adapterCallbackInfo); +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); +#endif IM_ASSERT(local_adapter && "Error on Adapter request"); return local_adapter; } @@ -377,8 +379,12 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) deviceCallbackInfo.userdata1 = &local_device; WGPUFuture future = wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); +#else + IM_UNUSED(instance); +#endif IM_ASSERT(local_device && "Error on Device request"); return local_device; } diff --git a/examples/example_sdl3_wgpu/main.cpp b/examples/example_sdl3_wgpu/main.cpp index 393c7f44f..c66748f80 100644 --- a/examples/example_sdl3_wgpu/main.cpp +++ b/examples/example_sdl3_wgpu/main.cpp @@ -373,8 +373,10 @@ static WGPUAdapter RequestAdapter(WGPUInstance& instance) adapterCallbackInfo.userdata1 = &local_adapter; WGPUFuture future = wgpuInstanceRequestAdapter(instance, &adapter_options, adapterCallbackInfo); +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); +#endif IM_ASSERT(local_adapter && "Error on Adapter request"); return local_adapter; } @@ -388,8 +390,12 @@ static WGPUDevice RequestDevice(WGPUInstance& instance, WGPUAdapter& adapter) deviceCallbackInfo.userdata1 = &local_device; WGPUFuture future = wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) WGPUFutureWaitInfo waitInfo = { future, false }; wgpuInstanceWaitAny(instance, 1, &waitInfo, ~0ull); +#else + IM_UNUSED(instance); +#endif IM_ASSERT(local_device && "Error on Device request"); return local_device; } From 19ead810d2496e6da0d3d3984ed68c211ffa004f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 16:37:00 +0200 Subject: [PATCH 186/254] ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by BeginDisabled() so disabled color buttons have the same color. (#9511) --- docs/CHANGELOG.txt | 2 ++ imgui_draw.cpp | 2 +- imgui_widgets.cpp | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 492b9eff8..4b79c8095 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,8 @@ Other Changes: to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of ProggyClean on most Apple/Retina setups by default. - Misc: + - ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by BeginDisabled() so + disabled color buttons have the same color as non-disabled oness. (#9511) - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] - ImDrawList: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b4739b8be..e42014745 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -6214,7 +6214,7 @@ ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRe void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags = ImDrawFlags_RoundCornersDefault_; + flags = ImDrawFlags_RoundCornersAll; if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e2a6771bd..bffd52e1d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6129,6 +6129,10 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl const bool is_readonly = ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; g.NextItemData.ClearFlags(); + const float backup_alpha = g.Style.Alpha; + if (g.DisabledStackSize > 0) + g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. + PushID(label); const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); if (set_current_color_edit_id) @@ -6468,7 +6472,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl { float alpha = ImSaturate(col[3]); ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); - RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK); float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); @@ -6486,6 +6490,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl g.ColorEditCurrentID = 0; PopID(); + if (g.DisabledStackSize > 0) + g.Style.Alpha = backup_alpha; + return value_changed; } @@ -6528,6 +6535,10 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. bb_inner.Expand(off); } + + const float backup_alpha = g.Style.Alpha; + if (g.DisabledStackSize > 0) + g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); @@ -6546,6 +6557,8 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl else window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); } + if (g.DisabledStackSize > 0) + g.Style.Alpha = backup_alpha; RenderNavCursor(bb, id); if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { From fd74775a0d73ce0c6b1bbd6f18793936e94a3c2a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 17:13:17 +0200 Subject: [PATCH 187/254] ColorButton: fixed double style alpha multiply. Remove call to GetColorU32() in RenderColorRectWithAlphaCheckerboard() for consistency. (#9511) The main fix is the change from GetColorU32() to ColorConvertFloat4ToU32() in calling code. The change from GetColorU32() to manual multiply in RenderColorRectWithAlphaCheckerboard() is merely for consistency with other ::Render calls. --- imgui_draw.cpp | 9 +++++---- imgui_internal.h | 2 +- imgui_widgets.cpp | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e42014745..b7128ccf6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -6210,15 +6210,16 @@ ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRe // Helper for ColorPicker4() // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. // Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. -// FIXME: uses ImGui::GetColorU32 -void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) +void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float alpha, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags = ImDrawFlags_RoundCornersAll; if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { - ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); - ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); + IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f); + const ImU32 a_mask = (ImU32)(alpha * 255.0f) << IM_COL32_A_SHIFT; + ImU32 col_bg1 = (ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col) & ~IM_COL32_A_MASK) | a_mask; + ImU32 col_bg2 = (ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col) & ~IM_COL32_A_MASK) | a_mask; draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); int yi = 0; diff --git a/imgui_internal.h b/imgui_internal.h index 631216426..01474fbac 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3725,7 +3725,7 @@ namespace ImGui IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorComponentMarker(const ImRect& bb, ImU32 col, float rounding); - IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); + IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float alpha, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None, float rounding = -1.0f); // Navigation highlight #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None) { RenderNavCursor(bb, id, flags); } // Renamed in 1.91.4 diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index bffd52e1d..91d95d2de 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6472,7 +6472,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl { float alpha = ImSaturate(col[3]); ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); - RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), style.Alpha, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK); float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); @@ -6543,7 +6543,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl { float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, ColorConvertFloat4ToU32(col_rgb), g.Style.Alpha, grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); else window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); @@ -6553,7 +6553,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb; if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, ColorConvertFloat4ToU32(col_source), g.Style.Alpha, grid_step, ImVec2(off, off), rounding); else window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); } From ebc7307db56654a60ff1c39150da32942c1568c8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 31 Aug 2026 17:27:03 +0200 Subject: [PATCH 188/254] ColorButton, RenderColorRectWithAlphaCheckerboard: fixed issues/inconsistencies with checkerboard rendering when combining color alpha and global style alpha. (#9511) Cleanup hacks in ColorButton(). Amend d851775c80a, e9ff7162b. --- docs/CHANGELOG.txt | 2 ++ imgui_draw.cpp | 17 ++++++++++++----- imgui_widgets.cpp | 19 ++++++------------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4b79c8095..c5ffda1a1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,6 +62,8 @@ Other Changes: - Misc: - ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by BeginDisabled() so disabled color buttons have the same color as non-disabled oness. (#9511) + - ColorButton: fixed issues/inconsistencies with checkerboard rendering when combining + color alpha and global style alpha. (#9511) [@enjmiah, @ocornut] - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] - ImDrawList: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b7128ccf6..5be20b1d5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -6220,17 +6220,23 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p const ImU32 a_mask = (ImU32)(alpha * 255.0f) << IM_COL32_A_SHIFT; ImU32 col_bg1 = (ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col) & ~IM_COL32_A_MASK) | a_mask; ImU32 col_bg2 = (ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col) & ~IM_COL32_A_MASK) | a_mask; - draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); + const bool dual_layer = (alpha == 1.0f); // Dual layer is faster but incorrect if blending. + if (dual_layer) + draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); int yi = 0; for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) { - float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + float y1 = ImClamp((float)(int)y, p_min.y, p_max.y), y2 = ImMin((float)(int)(y + grid_step), p_max.y); if (y2 <= y1) continue; - for (float x = p_min.x + grid_off.x + ((yi ^ 1) & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) + + const float x_start_off = dual_layer ? (((yi ^ 1) & 1) * grid_step) : 0.0f; + const float x_step = dual_layer ? (grid_step * 2.0f) : (grid_step); + int xi = 0; + for (float x = p_min.x + grid_off.x + x_start_off; x < p_max.x; x += x_step, xi++) { - float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + float x1 = ImClamp((float)(int)x, p_min.x, p_max.x), x2 = ImMin((float)(int)(x + grid_step), p_max.x); if (x2 <= x1) continue; ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; // FIXME: Could use CalcRoundingFlagsForRectInRect() @@ -6239,7 +6245,8 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p // Combine flags cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); - draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags); + ImU32 col_bg = dual_layer ? col_bg2 : (((yi + xi) & 1) ? col_bg2 : col_bg1); + draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg, rounding, cell_flags); } } } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 91d95d2de..6f4d4313f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6528,34 +6528,27 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); float grid_step = ImMin(size.x, size.y) / 2.99f; float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); - ImRect bb_inner = bb; - float off = 0.0f; - if ((flags & ImGuiColorEditFlags_NoBorder) == 0) - { - off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. - bb_inner.Expand(off); - } const float backup_alpha = g.Style.Alpha; if (g.DisabledStackSize > 0) g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { - float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); + float mid_x = IM_ROUND((bb.Min.x + bb.Max.x) * 0.5f); if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, ColorConvertFloat4ToU32(col_rgb), g.Style.Alpha, grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, ColorConvertFloat4ToU32(col_rgb), g.Style.Alpha, grid_step, ImVec2(-grid_step, 0.0f), rounding, ImDrawFlags_RoundCornersRight); else - window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); - window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); + window->DrawList->AddRectFilled(ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(bb.Min, ImVec2(mid_x, bb.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); } else { // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb; if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, ColorConvertFloat4ToU32(col_source), g.Style.Alpha, grid_step, ImVec2(off, off), rounding); + RenderColorRectWithAlphaCheckerboard(window->DrawList, bb.Min, bb.Max, ColorConvertFloat4ToU32(col_source), g.Style.Alpha, grid_step, ImVec2(0.0f, 0.0f), rounding); else - window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(col_source), rounding); } if (g.DisabledStackSize > 0) g.Style.Alpha = backup_alpha; From e0537bf72bd6da11a62b47020fd97cf5da0e3110 Mon Sep 17 00:00:00 2001 From: nilFinx Date: Tue, 1 Sep 2026 03:42:46 +1100 Subject: [PATCH 189/254] Fixed build issue with MacPorts Clang 16 (missing TARGET_OS_OSX). (#9498) --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index c4ac84770..26a9a3528 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16241,7 +16241,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* t ::CloseClipboard(); } -#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS) +#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS) #include // Use old API to avoid need for separate .mm file static PasteboardRef main_clipboard = 0; From f562ab8e329f19b606e67c52a87ca8abab752566 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 1 Sep 2026 16:21:37 +0200 Subject: [PATCH 190/254] DrawList: reworked assert in PushTexture() to allow convenience grace period of using texture already queued for destroy during the same frame. (#9528, #8465) Amend 131f5c57ab --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 2 +- imgui_draw.cpp | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c5ffda1a1..d04a67b13 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -70,6 +70,9 @@ Other Changes: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. - Circle and Curves tessellation error are scaled accordingly. + - Reworked assert in PushTexture() to allow convenience grace period of using a + texture that was queue for destroy during the frame. Make it safe to stick to + an existing texture during the frame. (#9528, #8465) - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without diff --git a/imgui.cpp b/imgui.cpp index 26a9a3528..d44fe54dc 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16812,7 +16812,7 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRe Checkbox("Show used rect", &cfg->ShowTextureUsedRect); PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); ImVec2 p = GetCursorScreenPos(); - if (tex->WantDestroyNextFrame) + if (tex->Status == ImTextureStatus_WantDestroy || tex->Status == ImTextureStatus_Destroyed) Dummy(ImVec2((float)tex->Width, (float)tex->Height)); else ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 5be20b1d5..bf7aebad1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -699,7 +699,7 @@ void ImDrawList::PushTexture(ImTextureRef tex_ref) _TextureStack.push_back(tex_ref); _CmdHeader.TexRef = tex_ref; if (tex_ref._TexData != NULL) - IM_ASSERT(tex_ref._TexData->WantDestroyNextFrame == false); + IM_ASSERT(tex_ref._TexData->Status != ImTextureStatus_WantDestroy && tex_ref._TexData->Status != ImTextureStatus_Destroyed); _OnChangedTexture(); } From a15761f11c7c2e4358e8ac679395e58bd4b956bd Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 1 Sep 2026 20:02:12 +0200 Subject: [PATCH 191/254] TreeNode: fixed UB shifts and general tree issues when using ImGuiTreeNodeFlags_DrawLinesXXX flags and over 32 deep. (#9509) + fix minor typo (#9533) --- docs/BACKENDS.md | 2 +- docs/CHANGELOG.txt | 3 +++ imgui_internal.h | 2 +- imgui_widgets.cpp | 26 ++++++++++++++++---------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/BACKENDS.md b/docs/BACKENDS.md index bab76fb60..5aa072092 100644 --- a/docs/BACKENDS.md +++ b/docs/BACKENDS.md @@ -72,7 +72,7 @@ For example, the [example_win32_directx11](https://github.com/ocornut/imgui/tree In the [backends/](https://github.com/ocornut/imgui/blob/master/backends) folder: -List of Platforms Backends: +List of Platform Backends: imgui_impl_android.cpp ; Android native app API imgui_impl_glfw.cpp ; GLFW (Windows, macOS, Linux, etc.) http://www.glfw.org/ diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d04a67b13..f425494dc 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,9 @@ Other Changes: - Drags, Sliders: - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] +- TreeNode: + - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX + features or other features requiring stack storage. (#9509) [@lasrod] - Fonts: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of diff --git a/imgui_internal.h b/imgui_internal.h index 01474fbac..109bd43e1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2692,7 +2692,7 @@ struct IMGUI_API ImGuiWindowTempData ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement int TreeDepth; // Current tree depth. - ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. Sync any changes with TREE_NODE_MAX_DEPTH/TREE_NODE_GET_DEPTH_MASK! ImU32 TreeRecordsClippedNodesY2Mask; // Store whether we should keep recording Y2. Cleared when passing clip max. Equivalent TreeHasStackDataDepthMask value should always be set. ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6f4d4313f..752910810 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6895,7 +6895,7 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) } // Store ImGuiTreeNodeStackData for just submitted node. -// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. +// Currently only supports 32 level deep. Easy to increase to e.g. 64. static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) { ImGuiContext& g = *GImGui; @@ -6918,6 +6918,9 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } +#define TREE_NODE_MAX_DEPTH 32 +#define TREE_NODE_GET_DEPTH_MASK(_DEPTH) (((ImU32)(_DEPTH) < TREE_NODE_MAX_DEPTH) ? (1u << (_DEPTH)) : 0u) + bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); @@ -6994,7 +6997,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0) flags |= g.Style.TreeLinesFlags; const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f); - if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen) && window->DC.TreeDepth < TREE_NODE_MAX_DEPTH) { store_tree_node_stack_data = draw_tree_lines; if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive) @@ -7005,12 +7008,16 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; if (!is_visible) { - if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1)))) + if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) { - ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; - parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. - if (frame_bb.Min.y >= window->ClipRect.Max.y) - window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done + const ImU32 tree_depth_mask = TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth - 1); + if (window->DC.TreeRecordsClippedNodesY2Mask & tree_depth_mask) + { + ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. + if (frame_bb.Min.y >= window->ClipRect.Max.y) + window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask; // Done + } } if (is_open && store_tree_node_stack_data) TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() @@ -7207,7 +7214,7 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0) + if ((window->DC.TreeHasStackDataDepthMask & TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth - 1)) == 0) return; ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; @@ -7291,8 +7298,7 @@ void ImGui::TreePop() Unindent(); window->DC.TreeDepth--; - ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - + ImU32 tree_depth_mask = TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth); if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) { const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; From e2e9533991233d598459f03714fe7f732168813e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 2 Sep 2026 16:26:11 +0200 Subject: [PATCH 192/254] Added IM_NODEBUGSTEP helper. Tagged ImVec2, ImVec4, ImRect and various operators. cc #3038. Amend 1bc6f82091, 301c956039 --- docs/CHANGELOG.txt | 8 ++- imconfig.h | 10 +-- imgui.h | 108 ++++++++++++++++------------- imgui_internal.h | 34 ++++----- misc/debuggers/README.txt | 28 ++++++-- misc/debuggers/imgui.gdb | 1 + misc/debuggers/imgui.natstepfilter | 2 + 7 files changed, 114 insertions(+), 77 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f425494dc..2cb5121bf 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,13 +62,19 @@ Other Changes: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of ProggyClean on most Apple/Retina setups by default. -- Misc: +- ColorEdit: - ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by BeginDisabled() so disabled color buttons have the same color as non-disabled oness. (#9511) - ColorButton: fixed issues/inconsistencies with checkerboard rendering when combining color alpha and global style alpha. (#9511) [@enjmiah, @ocornut] +- Misc: - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] + - Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC) + or `[[gnu::artificial]]` (Clang/GCC) in order to provide a hint to debuggers to skip + trivial functions when stepping into. + - Tagged various trivial functions using `IM_NODEBUGSTEP`: `ImVec2()`, `ImVec4()` etc. + and various operators. - ImDrawList: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. diff --git a/imconfig.h b/imconfig.h index fd1ed81f3..a25fa89af 100644 --- a/imconfig.h +++ b/imconfig.h @@ -107,12 +107,12 @@ // This will be inlined as part of ImVec2 and ImVec4 class declarations. /* #define IM_VEC2_CLASS_EXTRA \ - constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ - operator MyVec2() const { return MyVec2(x,y); } + IM_NODEBUGSTEP constexpr inline ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ + IM_NODEBUGSTEP inline operator MyVec2() const { return MyVec2(x,y); } -#define IM_VEC4_CLASS_EXTRA \ - constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ - operator MyVec4() const { return MyVec4(x,y,z,w); } +#define IM_VEC4_CLASS_EXTRA \ + IM_NODEBUGSTEP constexpr inline ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ + IM_NODEBUGSTEP inline operator MyVec4() const { return MyVec4(x,y,z,w); } */ //---- ...Or use Dear ImGui's own basic math operators in every file including imgui.h: //#ifndef IMGUI_DEFINE_MATH_OPERATORS diff --git a/imgui.h b/imgui.h index 07175b032..770c14a44 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.93.0 WIP" -#define IMGUI_VERSION_NUM 19294 +#define IMGUI_VERSION_NUM 19295 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -128,6 +128,16 @@ Index of this file: #define IM_MSVC_RUNTIME_CHECKS_RESTORE #endif +// Alternative to using a .natstepfilter file or other scripts in misc/debuggers/ to skip debug-stepping selected trivial functions. +// If you get a compiler error or warning related to use, please report it to us! +#if (defined(__clang__) && (__clang_major__ >= 7)) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) +#define IM_NODEBUGSTEP [[gnu::artificial]] +#elif defined(_MSC_VER) && (_MSC_VER >= 1915) +#define IM_NODEBUGSTEP __declspec(non_user_code) +#else +#define IM_NODEBUGSTEP +#endif + // Warnings #ifdef _MSC_VER #pragma warning (push) @@ -294,11 +304,11 @@ typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data); IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec2 { - float x, y; - constexpr ImVec2() : x(0.0f), y(0.0f) { } - constexpr ImVec2(float _x, float _y) : x(_x), y(_y) { } - float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return ((float*)(void*)(char*)this)[idx]; } // We very rarely use this [] operator, so the assert overhead is fine. - float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return ((const float*)(const void*)(const char*)this)[idx]; } + float x, y; + IM_NODEBUGSTEP constexpr inline ImVec2() : x(0.0f), y(0.0f) { } + IM_NODEBUGSTEP constexpr inline ImVec2(float _x, float _y) : x(_x), y(_y) { } + float& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return ((float*)(void*)(char*)this)[idx]; } // We very rarely use this [] operator, so the assert overhead is fine. + float operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return ((const float*)(const void*)(const char*)this)[idx]; } #ifdef IM_VEC2_CLASS_EXTRA IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. #endif @@ -307,9 +317,9 @@ struct ImVec2 // ImVec4: 4D vector used to store clipping rectangles, colors etc. [Compile-time configurable type] struct ImVec4 { - float x, y, z, w; - constexpr ImVec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) { } - constexpr ImVec4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) { } + float x, y, z, w; + IM_NODEBUGSTEP constexpr inline ImVec4() : x(0.0f), y(0.0f), z(0.0f), w(0.0f) { } + IM_NODEBUGSTEP constexpr inline ImVec4(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) { } #ifdef IM_VEC4_CLASS_EXTRA IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4. #endif @@ -2259,13 +2269,13 @@ struct ImVector inline int size_in_bytes() const { return Size * (int)sizeof(T); } inline int max_size() const { return 0x7FFFFFFF / (int)sizeof(T); } inline int capacity() const { return Capacity; } - inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } - inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + IM_NODEBUGSTEP inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + IM_NODEBUGSTEP inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } - inline T* begin() { return Data; } - inline const T* begin() const { return Data; } - inline T* end() { return Data + Size; } - inline const T* end() const { return Data + Size; } + IM_NODEBUGSTEP inline T* begin() { return Data; } + IM_NODEBUGSTEP inline const T* begin() const { return Data; } + IM_NODEBUGSTEP inline T* end() { return Data + Size; } + IM_NODEBUGSTEP inline const T* end() const { return Data + Size; } inline T& front() { IM_ASSERT(Size > 0); return Data[0]; } inline const T& front() const { IM_ASSERT(Size > 0); return Data[0]; } inline T& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } @@ -2940,41 +2950,41 @@ struct ImGuiListClipper #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF // ImVec2 operators -inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } -inline ImVec2 operator*(const float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); } -inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -inline ImVec2 operator+(const ImVec2& lhs) { return lhs; } -inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } -inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } -inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } -inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } -inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } -inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } -inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } -inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } -inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } +IM_NODEBUGSTEP inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +IM_NODEBUGSTEP inline ImVec2 operator*(const float lhs, const ImVec2& rhs) { return ImVec2(lhs * rhs.x, lhs * rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +IM_NODEBUGSTEP inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +IM_NODEBUGSTEP inline ImVec2 operator+(const ImVec2& lhs) { return lhs; } +IM_NODEBUGSTEP inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } +IM_NODEBUGSTEP inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +IM_NODEBUGSTEP inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +IM_NODEBUGSTEP inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } +IM_NODEBUGSTEP inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } // ImVec4 operators -inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } -inline ImVec4 operator*(const float lhs, const ImVec4& rhs) { return ImVec4(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } -inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } -inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } -inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } -inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } -inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } -inline ImVec4 operator+(const ImVec4& lhs) { return lhs; } -inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } -inline ImVec4& operator*=(ImVec4& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs; lhs.w *= rhs; return lhs; } -inline ImVec4& operator/=(ImVec4& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs; lhs.w /= rhs; return lhs; } -inline ImVec4& operator+=(ImVec4& lhs, const ImVec4& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z; lhs.w += rhs.w; return lhs; } -inline ImVec4& operator-=(ImVec4& lhs, const ImVec4& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z; lhs.w -= rhs.w; return lhs; } -inline ImVec4& operator*=(ImVec4& lhs, const ImVec4& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; lhs.z *= rhs.z; lhs.w *= rhs.w; return lhs; } -inline ImVec4& operator/=(ImVec4& lhs, const ImVec4& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z; lhs.w /= rhs.w; return lhs; } -inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } -inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } +IM_NODEBUGSTEP inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } +IM_NODEBUGSTEP inline ImVec4 operator*(const float lhs, const ImVec4& rhs) { return ImVec4(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } +IM_NODEBUGSTEP inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } +IM_NODEBUGSTEP inline ImVec4 operator+(const ImVec4& lhs) { return lhs; } +IM_NODEBUGSTEP inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } +IM_NODEBUGSTEP inline ImVec4& operator*=(ImVec4& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs; lhs.w *= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator/=(ImVec4& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs; lhs.w /= rhs; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator+=(ImVec4& lhs, const ImVec4& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z; lhs.w += rhs.w; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator-=(ImVec4& lhs, const ImVec4& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z; lhs.w -= rhs.w; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator*=(ImVec4& lhs, const ImVec4& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; lhs.z *= rhs.z; lhs.w *= rhs.w; return lhs; } +IM_NODEBUGSTEP inline ImVec4& operator/=(ImVec4& lhs, const ImVec4& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z; lhs.w /= rhs.w; return lhs; } +IM_NODEBUGSTEP inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } +IM_NODEBUGSTEP inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } IM_MSVC_RUNTIME_CHECKS_RESTORE #endif diff --git a/imgui_internal.h b/imgui_internal.h index 109bd43e1..19d7043b8 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -565,25 +565,25 @@ IM_MSVC_RUNTIME_CHECKS_OFF struct ImVec1 { float x; - constexpr ImVec1() : x(0.0f) { } - constexpr ImVec1(float _x) : x(_x) { } + IM_NODEBUGSTEP constexpr inline ImVec1() : x(0.0f) { } + IM_NODEBUGSTEP constexpr inline ImVec1(float _x) : x(_x) { } }; // Helper: ImVec2i (2D vector, integer) struct ImVec2i { int x, y; - constexpr ImVec2i() : x(0), y(0) {} - constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} + IM_NODEBUGSTEP constexpr inline ImVec2i() : x(0), y(0) {} + IM_NODEBUGSTEP constexpr inline ImVec2i(int _x, int _y) : x(_x), y(_y) {} }; // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { short x, y; - constexpr ImVec2ih() : x(0), y(0) {} - constexpr ImVec2ih(short _x, short _y) : x(_x), y(_y) {} - constexpr explicit ImVec2ih(const ImVec2& rhs) : x((short)rhs.x), y((short)rhs.y) {} + IM_NODEBUGSTEP constexpr inline ImVec2ih() : x(0), y(0) {} + IM_NODEBUGSTEP constexpr inline ImVec2ih(short _x, short _y) : x(_x), y(_y) {} + IM_NODEBUGSTEP constexpr inline explicit ImVec2ih(const ImVec2& rhs) : x((short)rhs.x), y((short)rhs.y) {} }; // Helper: ImRect (2D axis aligned bounding-box) @@ -593,10 +593,10 @@ struct IMGUI_API ImRect ImVec2 Min; // Upper-left ImVec2 Max; // Lower-right - constexpr ImRect() : Min(0.0f, 0.0f), Max(0.0f, 0.0f) {} - constexpr ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} - constexpr ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} - constexpr ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + IM_NODEBUGSTEP constexpr inline ImRect() : Min(0.0f, 0.0f), Max(0.0f, 0.0f) {} + IM_NODEBUGSTEP constexpr inline ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} + IM_NODEBUGSTEP constexpr inline ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} + IM_NODEBUGSTEP constexpr inline ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } @@ -698,13 +698,13 @@ struct ImSpan inline bool empty() const { return Data == DataEnd; } inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } inline int size_in_bytes() const { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); } - inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } - inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } - inline T* begin() { return Data; } - inline const T* begin() const { return Data; } - inline T* end() { return DataEnd; } - inline const T* end() const { return DataEnd; } + IM_NODEBUGSTEP inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + IM_NODEBUGSTEP inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + IM_NODEBUGSTEP inline T* begin() { return Data; } + IM_NODEBUGSTEP inline const T* begin() const { return Data; } + IM_NODEBUGSTEP inline T* end() { return DataEnd; } + IM_NODEBUGSTEP inline const T* end() const { return DataEnd; } // Utilities inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } diff --git a/misc/debuggers/README.txt b/misc/debuggers/README.txt index ad6f491fe..22a66a075 100644 --- a/misc/debuggers/README.txt +++ b/misc/debuggers/README.txt @@ -1,12 +1,14 @@ -HELPER FILES FOR POPULAR DEBUGGERS +----------------------------------------------------------------------------- + HELPER FILES FOR POPULAR DEBUGGERS +----------------------------------------------------------------------------- imgui.gdb - GDB: disable stepping into trivial functions. + GDB: disable stepping into trivial functions (*1). (read comments inside file for details) imgui.natstepfilter - Visual Studio Debugger: disable stepping into trivial functions. + Visual Studio Debugger: disable stepping into trivial functions (*1). (read comments inside file for details) imgui.natvis @@ -15,7 +17,23 @@ imgui.natvis (read comments inside file for details) imgui_lldb.py - LLDB-based debuggers (*): synthetic children provider and summaries for Dear ImGui types. + LLDB-based debuggers (*2): synthetic children provider and summaries for Dear ImGui types. With this, types like ImVector<> will be displayed nicely in the debugger. (read comments inside file for details) - (*) Xcode, Android Studio, may be used from VS Code, C++Builder, CLion, Eclipse etc. + (*2) Xcode, Android Studio, may be used from VS Code, C++Builder, CLion, Eclipse etc. + +----------------------------------------------------------------------------- + NEW! DISABLED STEPPING INTO TRIVIAL FUNCTIONS +----------------------------------------------------------------------------- + +Since 1.93.0 WIP (September 2026), +Dear ImGui uses a IM_NODEBUGSTEP macro to tag functions with __declspec(non_user_code) +or [[gnu::artificial]] to automatically tell debuggers to skip them. + +(*1) Assuming the new IM_NODEBUGSTEP macro works, using imgui.natstepfilter etc. +may become unnecessary. Exact side effects on user experience yet to be clarified. + +Additional references: + https://maskray.me/blog/skipping-boring-functions-in-debuggers + and https://news.ycombinator.com/item?id=42547576 + diff --git a/misc/debuggers/imgui.gdb b/misc/debuggers/imgui.gdb index 000ff6eb9..1229d0588 100644 --- a/misc/debuggers/imgui.gdb +++ b/misc/debuggers/imgui.gdb @@ -9,4 +9,5 @@ # * https://sourceware.org/gdb/current/onlinedocs/gdb/Init-File-in-the-Current-Directory.html#Init-File-in-the-Current-Directory # Disable stepping into trivial functions +# ...since Dear ImGui 1.93.0 WIP (September 2026): we use a IM_NODEBUGSTEP() macro to provide the equivalent feature via [[gnu::artificial]]. skip -rfunction Im(Vec2|Vec4|Strv|Vector|Span)::.+ diff --git a/misc/debuggers/imgui.natstepfilter b/misc/debuggers/imgui.natstepfilter index 6825c9346..82a30aa79 100644 --- a/misc/debuggers/imgui.natstepfilter +++ b/misc/debuggers/imgui.natstepfilter @@ -14,6 +14,8 @@ If you have multiple VS version installed, the version that matters is the one y of (not the compiling toolset). This is supported since Visual Studio 2012. More information at: https://docs.microsoft.com/en-us/visualstudio/debugger/just-my-code?view=vs-2019#BKMK_C___Just_My_Code + +Since Dear ImGui 1.93.0 WIP (September 2026): we use a IM_NODEBUGSTEP() macro to provide the equivalent feature via __declspec(non_user_code). --> From 4dbb921b228c33a78eca4914898709b7ac93671e Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 2 Sep 2026 17:15:39 +0200 Subject: [PATCH 193/254] Examples: disabled /JMC in Visual Studio projects (unnecessary overhead). --- docs/CHANGELOG.txt | 1 + examples/example_allegro5/example_allegro5.vcxproj | 2 ++ examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj | 2 ++ examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj | 2 ++ examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj | 2 ++ examples/example_glut_opengl2/example_glut_opengl2.vcxproj | 2 ++ examples/example_null/example_null.vcxproj | 2 ++ examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj | 2 ++ examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj | 2 ++ examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj | 2 ++ .../example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2.vcxproj | 2 ++ examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj | 2 ++ examples/example_sdl3_directx11/example_sdl3_directx11.vcxproj | 2 ++ examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj | 2 ++ examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3.vcxproj | 2 ++ .../example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3.vcxproj | 2 ++ examples/example_sdl3_vulkan/example_sdl3_vulkan.vcxproj | 2 ++ .../example_win32_directx10/example_win32_directx10.vcxproj | 2 ++ .../example_win32_directx11/example_win32_directx11.vcxproj | 2 ++ .../example_win32_directx12/example_win32_directx12.vcxproj | 2 ++ examples/example_win32_directx9/example_win32_directx9.vcxproj | 2 ++ examples/example_win32_opengl3/example_win32_opengl3.vcxproj | 2 ++ examples/example_win32_vulkan/example_win32_vulkan.vcxproj | 2 ++ 23 files changed, 45 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2cb5121bf..1749549ce 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -90,6 +90,7 @@ Other Changes: when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] - Examples: + - MSVC: disabled /JMC in Visual Studio projects (unnecessary overhead). - GLFW/SDL2/SDL3+WebGPU: fixed running with wgpu-native missing `wgpuInstanceWaitAny()`. (#9377) - SDL2+Metal: create Metal context without using a `SDL_Renderer`. - QNX+OpenGL3, QNX+Vulkan: added QNX Screen examples. (#9492) [@mgorchak-blackberry] diff --git a/examples/example_allegro5/example_allegro5.vcxproj b/examples/example_allegro5/example_allegro5.vcxproj index aa8a752d7..36c55bb10 100644 --- a/examples/example_allegro5/example_allegro5.vcxproj +++ b/examples/example_allegro5/example_allegro5.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj index bce5781f4..18d95894f 100644 --- a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj +++ b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;..\libs\glfw\include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;..\libs\glfw\include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj index cd59daed3..3e9d71e13 100644 --- a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj +++ b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;..\libs\glfw\include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;..\libs\glfw\include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj index 6f44abfec..492aa0e3f 100644 --- a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj +++ b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj @@ -93,6 +93,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;..\libs\glfw\include;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true @@ -109,6 +110,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;..\libs\glfw\include;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_glut_opengl2/example_glut_opengl2.vcxproj b/examples/example_glut_opengl2/example_glut_opengl2.vcxproj index 2e12665d5..bdd9af440 100644 --- a/examples/example_glut_opengl2/example_glut_opengl2.vcxproj +++ b/examples/example_glut_opengl2/example_glut_opengl2.vcxproj @@ -92,6 +92,7 @@ Disabled $(GLUT_INCLUDE_DIR);..\..;..\..\backends;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled $(GLUT_INCLUDE_DIR);..\..;..\..\backends;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_null/example_null.vcxproj b/examples/example_null/example_null.vcxproj index 75fd43b44..61e388f96 100644 --- a/examples/example_null/example_null.vcxproj +++ b/examples/example_null/example_null.vcxproj @@ -88,6 +88,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories); /utf-8 %(AdditionalOptions) + false true @@ -102,6 +103,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories); /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj b/examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj index 2bcc7097c..afa9d5de4 100644 --- a/examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj +++ b/examples/example_sdl2_directx11/example_sdl2_directx11.vcxproj @@ -93,6 +93,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -108,6 +109,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj b/examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj index aca6edc5d..35b7ed7df 100644 --- a/examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj +++ b/examples/example_sdl2_opengl2/example_sdl2_opengl2.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj b/examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj index d0cb96ba1..b5a80197e 100644 --- a/examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj +++ b/examples/example_sdl2_opengl3/example_sdl2_opengl3.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2.vcxproj b/examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2.vcxproj index 86b883a69..9d7951be8 100644 --- a/examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2.vcxproj +++ b/examples/example_sdl2_sdlrenderer2/example_sdl2_sdlrenderer2.vcxproj @@ -93,6 +93,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -108,6 +109,7 @@ Disabled ..\..;..\..\backends;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj b/examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj index 4bedc45d8..b2269bc9d 100644 --- a/examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj +++ b/examples/example_sdl2_vulkan/example_sdl2_vulkan.vcxproj @@ -93,6 +93,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true @@ -109,6 +110,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL2_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL2;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl3_directx11/example_sdl3_directx11.vcxproj b/examples/example_sdl3_directx11/example_sdl3_directx11.vcxproj index f38d290c6..057874196 100644 --- a/examples/example_sdl3_directx11/example_sdl3_directx11.vcxproj +++ b/examples/example_sdl3_directx11/example_sdl3_directx11.vcxproj @@ -93,6 +93,7 @@ Disabled ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -108,6 +109,7 @@ Disabled ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj b/examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj index c470f88bb..dbe1fa915 100644 --- a/examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj +++ b/examples/example_sdl3_opengl3/example_sdl3_opengl3.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3.vcxproj b/examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3.vcxproj index 02b0d35f2..bd83258d8 100644 --- a/examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3.vcxproj +++ b/examples/example_sdl3_sdlgpu3/example_sdl3_sdlgpu3.vcxproj @@ -93,6 +93,7 @@ ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true @@ -109,6 +110,7 @@ ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3.vcxproj b/examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3.vcxproj index f42014544..35fc962ad 100644 --- a/examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3.vcxproj +++ b/examples/example_sdl3_sdlrenderer3/example_sdl3_sdlrenderer3.vcxproj @@ -92,6 +92,7 @@ Disabled ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true @@ -107,6 +108,7 @@ Disabled ..\..;..\..\backends;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_sdl3_vulkan/example_sdl3_vulkan.vcxproj b/examples/example_sdl3_vulkan/example_sdl3_vulkan.vcxproj index af1144789..c3a200cf5 100644 --- a/examples/example_sdl3_vulkan/example_sdl3_vulkan.vcxproj +++ b/examples/example_sdl3_vulkan/example_sdl3_vulkan.vcxproj @@ -93,6 +93,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true @@ -109,6 +110,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;%SDL3_DIR%\include;$(VcpkgCurrentInstalledDir)include\SDL3;%(AdditionalIncludeDirectories) _MBCS;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_win32_directx10/example_win32_directx10.vcxproj b/examples/example_win32_directx10/example_win32_directx10.vcxproj index 7fb767702..add37ed97 100644 --- a/examples/example_win32_directx10/example_win32_directx10.vcxproj +++ b/examples/example_win32_directx10/example_win32_directx10.vcxproj @@ -88,6 +88,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories); /utf-8 %(AdditionalOptions) + false true @@ -102,6 +103,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories); /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_win32_directx11/example_win32_directx11.vcxproj b/examples/example_win32_directx11/example_win32_directx11.vcxproj index 9e89353d4..ed0b509a8 100644 --- a/examples/example_win32_directx11/example_win32_directx11.vcxproj +++ b/examples/example_win32_directx11/example_win32_directx11.vcxproj @@ -88,6 +88,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories); /utf-8 %(AdditionalOptions) + false true @@ -102,6 +103,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories); /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_win32_directx12/example_win32_directx12.vcxproj b/examples/example_win32_directx12/example_win32_directx12.vcxproj index c4b81e76f..27b501d85 100644 --- a/examples/example_win32_directx12/example_win32_directx12.vcxproj +++ b/examples/example_win32_directx12/example_win32_directx12.vcxproj @@ -89,6 +89,7 @@ ..\..;..\..\backends;%(AdditionalIncludeDirectories) _UNICODE;UNICODE;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true @@ -104,6 +105,7 @@ ..\..;..\..\backends;%(AdditionalIncludeDirectories) _UNICODE;UNICODE;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_win32_directx9/example_win32_directx9.vcxproj b/examples/example_win32_directx9/example_win32_directx9.vcxproj index c5efda38b..84e16c9c6 100644 --- a/examples/example_win32_directx9/example_win32_directx9.vcxproj +++ b/examples/example_win32_directx9/example_win32_directx9.vcxproj @@ -88,6 +88,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories);$(DXSDK_DIR)Include; /utf-8 %(AdditionalOptions) + false true @@ -102,6 +103,7 @@ Disabled ..\..;..\..\backends;%(AdditionalIncludeDirectories);$(DXSDK_DIR)Include; /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_win32_opengl3/example_win32_opengl3.vcxproj b/examples/example_win32_opengl3/example_win32_opengl3.vcxproj index b9b42677c..2ffb7d413 100644 --- a/examples/example_win32_opengl3/example_win32_opengl3.vcxproj +++ b/examples/example_win32_opengl3/example_win32_opengl3.vcxproj @@ -88,6 +88,7 @@ Disabled ..\..;..\..\backends; /utf-8 %(AdditionalOptions) + false true @@ -102,6 +103,7 @@ Disabled ..\..;..\..\backends; /utf-8 %(AdditionalOptions) + false true diff --git a/examples/example_win32_vulkan/example_win32_vulkan.vcxproj b/examples/example_win32_vulkan/example_win32_vulkan.vcxproj index f3ba4aa61..bede521e2 100644 --- a/examples/example_win32_vulkan/example_win32_vulkan.vcxproj +++ b/examples/example_win32_vulkan/example_win32_vulkan.vcxproj @@ -89,6 +89,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;%(AdditionalIncludeDirectories) _UNICODE;UNICODE;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true @@ -104,6 +105,7 @@ ..\..;..\..\backends;%VULKAN_SDK%\include;%(AdditionalIncludeDirectories) _UNICODE;UNICODE;%(PreprocessorDefinitions) /utf-8 %(AdditionalOptions) + false true From 1807b25b9aa09b28e01fb5e7dc4186277e4852c9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 3 Sep 2026 15:29:37 +0200 Subject: [PATCH 194/254] ScaleValueFromRatioT: removed unnecessary check, amend f27af1b20. --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 752910810..2a207f6f0 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3085,7 +3085,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T { result = ImLerp(v_min, v_max, t); } - else if (t < 1.0) + else // t is already < 1.0 here { // - For integer values we want the clicking position to match the grab box so we round above // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. From 7edd2b1f12b352f3b7fce9480110bd90cd17bc1f Mon Sep 17 00:00:00 2001 From: slowriot Date: Thu, 3 Sep 2026 14:42:24 +0100 Subject: [PATCH 195/254] Sliders, Drags: adjust ScaleValueFromRatioT() to increase remapping precision (in theory). (#9536) Omar: couldn't detect any output difference in test suite with large u64/s64 ranges drag/sliders. I believe this is entirely theoretical and it's principal value is to shut Linter warnings --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2a207f6f0..65d131fb6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3091,7 +3091,7 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t; + FLOATTYPE v_new_off_f = (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min) * (FLOATTYPE)t; result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); } } From 5316e7061425524682cafbafe8e0b86494a70470 Mon Sep 17 00:00:00 2001 From: Lukasz Stalmirski Date: Thu, 3 Sep 2026 15:59:00 +0200 Subject: [PATCH 196/254] Backends: Vulkan: support multiple Vulkan contexts with custom loader. (#6616) Vulkan function calls now go through a short trampoline function, only apply when IMGUI_IMPL_VULKAN_USE_LOADER is set. --- backends/imgui_impl_vulkan.cpp | 45 +++++++++++++++++++++++++++++++--- docs/CHANGELOG.txt | 1 + 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 57ebdb064..0e172bfb8 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -27,6 +27,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-03: Added for support for multiple Vulkan contexts with custom loaders. (#6616) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-04-22: *BREAKING CHANGE* redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. This change allows us to facilitate changing samplers, in line with other backends. // - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. @@ -230,10 +231,28 @@ static bool g_FunctionsLoaded = true; IMGUI_VULKAN_FUNC_MAP_MACRO(vkUpdateDescriptorSets) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkWaitForFences) +struct ImGui_ImplVulkan_Functions +{ // Define function pointers -#define IMGUI_VULKAN_FUNC_DEF(func) static PFN_##func func; +#define IMGUI_VULKAN_FUNC_DEF(func) PFN_##func pfn_##func; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_DEF) #undef IMGUI_VULKAN_FUNC_DEF +}; + +// Globally available function pointers used for initialization of the backend data +static ImGui_ImplVulkan_Functions ImGuiImplVulkanFuncs; + +// Multi Vulkan contexts support: wrap each Vulkan function pointer to properly dispatch it using the current backend data +static ImGui_ImplVulkan_Functions& ImGui_ImplVulkan_GetFunctions(); +#define IMGUI_VULKAN_FUNC_WRAPPER(func) \ + template \ + auto func(Args&&... args) -> decltype(ImGui_ImplVulkan_GetFunctions().pfn_##func(args...)) \ + { \ + return ImGui_ImplVulkan_GetFunctions().pfn_##func(args...); \ + } +IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_WRAPPER) +#undef IMGUI_VULKAN_FUNC_WRAPPER + #endif // IMGUI_IMPL_VULKAN_USE_LOADER #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING @@ -300,6 +319,11 @@ struct ImGui_ImplVulkan_Data // Render buffers for main window ImGui_ImplVulkan_WindowRenderBuffers MainWindowRenderBuffers; + // Vulkan function pointers loaded for this context +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER + ImGui_ImplVulkan_Functions Functions; +#endif + ImGui_ImplVulkan_Data() { memset((void*)this, 0, sizeof(*this)); @@ -434,6 +458,16 @@ static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER +// Get the Vulkan function pointers used by the current backend. +// If no backend is available, return the currently loaded global function pointers (ImGuiImplVulkanFuncs). +static ImGui_ImplVulkan_Functions& ImGui_ImplVulkan_GetFunctions() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + return bd ? bd->Functions : ImGuiImplVulkanFuncs; +} +#endif + static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -529,7 +563,7 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline constants[1] = 2.0f / draw_data->DisplaySize.y; constants[2] = -1.0f - draw_data->DisplayPos.x * constants[0]; // Translate constants[3] = -1.0f - draw_data->DisplayPos.y * constants[1]; - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4, constants); + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, (uint32_t)sizeof(float) * 4, constants); // Setup sampler vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); @@ -1321,8 +1355,8 @@ bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction( #ifdef IMGUI_IMPL_VULKAN_USE_LOADER #define IMGUI_VULKAN_FUNC_LOAD(func) \ - func = reinterpret_cast(loader_func(#func, user_data)); \ - if (func == nullptr) \ + ImGuiImplVulkanFuncs.pfn_##func = reinterpret_cast(loader_func(#func, user_data)); \ + if (ImGuiImplVulkanFuncs.pfn_##func == nullptr) \ return false; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_LOAD) #undef IMGUI_VULKAN_FUNC_LOAD @@ -1390,6 +1424,9 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) IM_ASSERT(info->PipelineInfoMain.RenderPass == VK_NULL_HANDLE); bd->VulkanInitInfo = *info; +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER + bd->Functions = ImGuiImplVulkanFuncs; +#endif VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(info->PhysicalDevice, &properties); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1749549ce..2ce07692c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -86,6 +86,7 @@ Other Changes: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without going through a SDL_Renderer. (#5592) [@lailoken] + - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] From 51c3c0c9920cec07d98aace659b9bb9831fbde83 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 3 Sep 2026 17:27:28 +0200 Subject: [PATCH 197/254] Sliders: moved code from SliderBehaviorT() to SliderBehavior(). Remove a level of indent too. --- imgui_widgets.cpp | 64 +++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 65d131fb6..19d03d0b1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2665,10 +2665,10 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + // Things we can do easily outside the DragBehaviorT<> template, saves code generation. ImGuiContext& g = *GImGui; if (g.ActiveId == id) { - // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) ClearActiveID(); else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) @@ -3140,37 +3140,24 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ float clicked_t = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse) { - if (!g.IO.MouseDown[0]) + const float mouse_abs_pos = g.IO.MousePos[axis]; + if (g.ActiveIdIsJustActivated) { - ClearActiveID(); - } - else - { - const float mouse_abs_pos = g.IO.MousePos[axis]; - if (g.ActiveIdIsJustActivated) - { - float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); - if (axis == ImGuiAxis_Y) - grab_t = 1.0f - grab_t; - const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); - const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. - g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; - } - if (slider_usable_sz > 0.0f) - clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) - clicked_t = 1.0f - clicked_t; - set_new_value = true; + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. + g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; } + if (slider_usable_sz > 0.0f) + clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + set_new_value = true; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { - if (g.ActiveIdIsJustActivated) - { - g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation - g.SliderCurrentAccumDirty = false; - } - float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis); if (input_delta != 0.0f) { @@ -3198,11 +3185,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } float delta = g.SliderCurrentAccum; - if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) - { - ClearActiveID(); - } - else if (g.SliderCurrentAccumDirty) + if (g.SliderCurrentAccumDirty) { clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); @@ -3228,7 +3211,6 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ else g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); } - g.SliderCurrentAccumDirty = false; } } @@ -3283,6 +3265,24 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() + // Things we can do easily outside the SliderBehaviorT<> template, saves code generation. + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) + { + if (g.ActiveIdIsJustActivated) + { + g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation + g.SliderCurrentAccumDirty = false; + } + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + } + switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } From f150bf09f97bc5e4fa663e578fd414b7fdb1e2e8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 3 Sep 2026 17:20:08 +0200 Subject: [PATCH 198/254] Drags, Sliders: ongoing drags may be cancelled using Escape or Gamepad Triangle, reverting to the initial value. (#8564, #9534) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 2ce07692c..c0953950e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,8 @@ Other Changes: - Drags, Sliders: - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] + - Ongoing drags may be cancelled using Escape or Gamepad Triangle, reverting + to the initial value. (#8564, #9534) - TreeNode: - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX features or other features requiring stack storage. (#9509) [@lasrod] diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 19d03d0b1..5de0dfb06 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2660,6 +2660,17 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const return true; } +static bool ShortcutsForCancel(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + bool is_cancel_with_keyboard = ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, id); + bool is_cancel_with_gamepad = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0 && ImGui::Shortcut(ImGuiKey_NavGamepadCancel, ImGuiInputFlags_None, id); + //bool is_cancel_with_mouse = ImGui::IsMouseClicked(ImGuiMouseButton_Right, ImGuiInputFlags_None, id); + //if (is_cancel_with_mouse) + // ImGui::SetKeyOwner(ImGuiKey_MouseRight, id); + return is_cancel_with_keyboard || is_cancel_with_gamepad; //|| is_cancel_with_mouse +} + bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. @@ -2673,6 +2684,15 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v ClearActiveID(); else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); + if (ShortcutsForCancel(id) && g.ActiveId == id) + { + // Canceling action reverts to initial value + size_t data_size = DataTypeGetInfo(data_type)->Size; + bool value_changed = memcmp(p_v, &g.ActiveIdValueOnActivation, data_size); + memcpy(p_v, &g.ActiveIdValueOnActivation, data_size); + ClearActiveID(); + return value_changed; + } } if (g.ActiveId != id) return false; @@ -3281,6 +3301,15 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); } + if (ShortcutsForCancel(id) && g.ActiveId == id) + { + // Canceling action reverts to initial value + size_t data_size = DataTypeGetInfo(data_type)->Size; + bool value_changed = memcmp(p_v, &g.ActiveIdValueOnActivation, data_size); + memcpy(p_v, &g.ActiveIdValueOnActivation, data_size); + ClearActiveID(); + return value_changed; + } } switch (data_type) From 96b6eb7728d298decdc939d4c698502220190050 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 4 Sep 2026 12:37:48 +0200 Subject: [PATCH 199/254] Fixed PVS-Studio false positives. Not sure if there's a way to report them. --- imgui_widgets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 5de0dfb06..71da3b4c3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2684,7 +2684,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v ClearActiveID(); else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); - if (ShortcutsForCancel(id) && g.ActiveId == id) + if (ShortcutsForCancel(id) && g.ActiveId == id) //-V560 { // Canceling action reverts to initial value size_t data_size = DataTypeGetInfo(data_type)->Size; @@ -3301,7 +3301,7 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); } - if (ShortcutsForCancel(id) && g.ActiveId == id) + if (ShortcutsForCancel(id) && g.ActiveId == id) //-V560 { // Canceling action reverts to initial value size_t data_size = DataTypeGetInfo(data_type)->Size; From 080319c4ce3df04c4bb6064b3a44e286674c886d Mon Sep 17 00:00:00 2001 From: SlowRiot Date: Sat, 5 Sep 2026 17:18:09 +0100 Subject: [PATCH 200/254] Backends: WebGPU: round framebuffer dimensions instead of truncating. (#9538, #9515, #8628) --- backends/imgui_impl_wgpu.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index cf8f7a969..b57035a06 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-08-31: Update to compile with wgpu-native 29.0. (#9377, #9532, #9530) // 2026-08-18: Fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport() when FramebufferScale is >1.0f. (#9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -436,8 +437,8 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas } // Setup viewport - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); wgpuRenderPassEncoderSetViewport(ctx, 0, 0, (float)fb_width, (float)fb_height, 0, 1); // Bind shader and vertex buffers @@ -460,8 +461,8 @@ static void ImGui_ImplWGPU_DrawCallback_SetSamplerNearest(const ImDrawList*, con void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder) { // Avoid rendering when minimized - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; From 4898533613abc4412601fdb87f775f8c399701fe Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 12:47:03 +0200 Subject: [PATCH 201/254] Backends: DX10, DX11, DX12, Metal/4, OpenGL2/3, SDLGPU3, Vulkan: round framebuffer dimensions instead of truncating. (#9538, #9515, #8628) --- backends/imgui_impl_dx10.cpp | 5 +++-- backends/imgui_impl_dx11.cpp | 4 ++-- backends/imgui_impl_dx12.cpp | 5 +++-- backends/imgui_impl_metal.mm | 9 +++++---- backends/imgui_impl_metal4.mm | 9 +++++---- backends/imgui_impl_opengl2.cpp | 5 +++-- backends/imgui_impl_opengl3.cpp | 5 +++-- backends/imgui_impl_sdlgpu3.cpp | 9 +++++---- backends/imgui_impl_vulkan.cpp | 5 +++-- examples/example_apple_opengl2/main.mm | 4 ++-- examples/example_apple_opengl3/main.mm | 4 ++-- 11 files changed, 36 insertions(+), 28 deletions(-) diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index 35378612d..7cf062cc7 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-04-23: DirectX10: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplDX10_RenderState. (#9378) // 2026-01-19: DirectX10: Added 'SamplerNearest' in ImGui_ImplDX10_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. @@ -106,8 +107,8 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* // Setup viewport D3D10_VIEWPORT vp = {}; - vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index 73f722a08..a15f1ab25 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -110,8 +110,8 @@ static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11D // Setup viewport D3D11_VIEWPORT vp = {}; - vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; - vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.Width = (float)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (float)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 04516eefd..811600b66 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-08-05: DirectX12: *BREAKING CHANGE* Removed support for legacy `ImGui_ImplDX12_Init()` signature obsoleted in 1.91.6 (2024-11-15) because it needed to forcefully disable support for ImGuiBackendFlags_RendererHasTextures. (#9487) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2025-10-11: DirectX12: Reuse texture upload buffer and grow it only when necessary. (#9002) @@ -183,8 +184,8 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic // Setup viewport D3D12_VIEWPORT vp = {}; - vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; - vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.Width = (float)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (float)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0.0f; diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 1c0660628..55b33de14 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-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) // 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) @@ -163,8 +164,8 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, idDisplaySize.x * draw_data->FramebufferScale.x), - .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .width = (double)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f), + .height = (double)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f), .znear = 0.0, .zfar = 1.0 }; @@ -203,8 +204,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 2866ed1e2..73f8c5905 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -19,6 +19,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-07: Metal 4: Added metal-cpp support. (#9461) // 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) @@ -162,8 +163,8 @@ static void ImGui_ImplMetal4_SetupRenderState(ImDrawData* draw_data, idDisplaySize.x * draw_data->FramebufferScale.x), - .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .width = (double)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f), + .height = (double)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f), .znear = 0.0, .zfar = 1.0 }; @@ -203,8 +204,8 @@ void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, idSharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index e6d195533..8fe9d489f 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -26,6 +26,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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) @@ -168,8 +169,8 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width == 0 || fb_height == 0) return; diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index e917fd36b..6d3e2751f 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-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. // 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) @@ -455,8 +456,8 @@ static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 9328b0a32..2a73f1b05 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-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplSDLGPU3_RenderState. (#9378) // 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) @@ -163,8 +164,8 @@ static void CreateOrResizeBuffers(SDL_GPUBuffer** buffer, SDL_GPUTransferBuffer* void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0) return; @@ -229,8 +230,8 @@ static void ImGui_ImplSDLGPU3_DrawCallback_SetSamplerNearest(const ImDrawList*, void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 0e172bfb8..795c87a1a 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -27,6 +27,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-09-03: Added for support for multiple Vulkan contexts with custom loaders. (#6616) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-04-22: *BREAKING CHANGE* redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. This change allows us to facilitate changing samplers, in line with other backends. @@ -587,8 +588,8 @@ void ImGui_ImplVulkan_DrawCallback_SetSamplerCustom(const ImDrawList*, const ImD void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index 65c169089..7cdc38f20 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -131,8 +131,8 @@ ImDrawData* draw_data = ImGui::GetDrawData(); [[self openGLContext] makeCurrentContext]; - GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); glViewport(0, 0, width, height); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); diff --git a/examples/example_apple_opengl3/main.mm b/examples/example_apple_opengl3/main.mm index 1541d5d07..241aa1332 100644 --- a/examples/example_apple_opengl3/main.mm +++ b/examples/example_apple_opengl3/main.mm @@ -131,8 +131,8 @@ ImDrawData* draw_data = ImGui::GetDrawData(); [[self openGLContext] makeCurrentContext]; - GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); glViewport(0, 0, width, height); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); From 4ebb04333b65f95feab684be6e4a4a8f1075ac4c Mon Sep 17 00:00:00 2001 From: Lukasz Stalmirski Date: Thu, 3 Sep 2026 21:40:20 +0200 Subject: [PATCH 202/254] IO: restore default platform handlers on ClearPlatformHandlers. (#9537, #8945, #2769) Amend d4f722d5b. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c0953950e..3068c9521 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -77,6 +77,8 @@ Other Changes: trivial functions when stepping into. - Tagged various trivial functions using `IM_NODEBUGSTEP`: `ImVec2()`, `ImVec4()` etc. and various operators. + - IO: restore default clipboard/ime/shell platform handlers on ClearPlatformHandlers(), fixing + issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] - ImDrawList: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. diff --git a/imgui.cpp b/imgui.cpp index d44fe54dc..c926fc754 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1387,10 +1387,9 @@ static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSetti static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); // Platform Dependents default implementation for ImGuiPlatformIO functions +static void InitializeDefaultPlatformHandlers(ImGuiPlatformIO& platform_io); static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx); static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text); -static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); -static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path); namespace ImGui { @@ -4486,10 +4485,7 @@ void ImGui::Initialize() LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_COUNTOF(GLocalizationEntriesEnUS)); // Setup default ImGuiPlatformIO clipboard/IME handlers. - g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations - g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; - g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; - g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; + InitializeDefaultPlatformHandlers(g.PlatformIO); // Setup session starting date #ifndef IMGUI_DISABLE_TIME_FUNCTIONS @@ -16101,6 +16097,7 @@ void ImGuiPlatformIO::ClearPlatformHandlers() Platform_OpenInShellUserData = NULL; Platform_SetImeDataFn = NULL; Platform_ImeUserData = NULL; + InitializeDefaultPlatformHandlers(*this); // Rrevents losing the functionality provided by Platform_*_DefaultImpl on backend re-creation. } void ImGuiPlatformIO::ClearRendererHandlers() @@ -16413,6 +16410,15 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG #endif // Default IME handlers +// Setup default ImGuiPlatformIO clipboard/IME handlers +static void InitializeDefaultPlatformHandlers(ImGuiPlatformIO& platform_io) +{ + platform_io.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; + platform_io.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; + platform_io.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; + platform_io.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; +} + //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- From 687f7fb8ac537bd276cac873ef004779521bc9c0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 16:16:55 +0200 Subject: [PATCH 203/254] Drags, Sliders: ongoing drags may be cancelled using Right-Mouse mouse button, reverting to the initial value. (#8564, #9534) --- docs/CHANGELOG.txt | 4 ++-- imgui.cpp | 8 ++++---- imgui_widgets.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3068c9521..4da90a036 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,8 +55,8 @@ Other Changes: - Drags, Sliders: - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] - - Ongoing drags may be cancelled using Escape or Gamepad Triangle, reverting - to the initial value. (#8564, #9534) + - Ongoing drags may be cancelled using right-mouse button, Escape, Gamepad Triangle. + Cancelling reverts to the initial value. (#8564, #9534) - TreeNode: - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX features or other features requiring stack storage. (#9509) [@lasrod] diff --git a/imgui.cpp b/imgui.cpp index c926fc754..3edc0bbc7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12792,7 +12792,7 @@ bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(mouse_button, id) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) return true; if (g.NavOpenContextMenuItemId == id && (IsItemFocused() || id == g.CurrentWindow->MoveId)) return true; @@ -12803,7 +12803,7 @@ bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(mouse_button, ImGuiKeyOwner_NoOwner) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) return true; if (g.NavOpenContextMenuWindowId && g.CurrentWindow->ID) @@ -12874,9 +12874,9 @@ bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flag ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "void_context"; - ImGuiID id = window->GetID(str_id); + ImGuiID id = window->GetID(str_id); // FIXME: Use a global ID? ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + if (IsMouseReleased(mouse_button, id) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) if (GetTopMostPopupModal() == NULL) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 71da3b4c3..84718fb1f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2665,10 +2665,10 @@ static bool ShortcutsForCancel(ImGuiID id) ImGuiContext& g = *GImGui; bool is_cancel_with_keyboard = ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, id); bool is_cancel_with_gamepad = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0 && ImGui::Shortcut(ImGuiKey_NavGamepadCancel, ImGuiInputFlags_None, id); - //bool is_cancel_with_mouse = ImGui::IsMouseClicked(ImGuiMouseButton_Right, ImGuiInputFlags_None, id); - //if (is_cancel_with_mouse) - // ImGui::SetKeyOwner(ImGuiKey_MouseRight, id); - return is_cancel_with_keyboard || is_cancel_with_gamepad; //|| is_cancel_with_mouse + bool is_cancel_with_mouse = ImGui::IsMouseReleased(ImGuiMouseButton_Right, id); + if (is_cancel_with_mouse) + ImGui::SetKeyOwner(ImGuiKey_MouseRight, id); + return is_cancel_with_keyboard || is_cancel_with_gamepad || is_cancel_with_mouse; } bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) From 53d421955fdb658e75b039b8210841ad691dafd1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 16:23:01 +0200 Subject: [PATCH 204/254] Amend Changelog. (#8564, #9534) --- docs/CHANGELOG.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4da90a036..47d921701 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,7 +55,7 @@ Other Changes: - Drags, Sliders: - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] - - Ongoing drags may be cancelled using right-mouse button, Escape, Gamepad Triangle. + - Ongoing drags may be cancelled using Right-Click, Escape or Gamepad Triangle. Cancelling reverts to the initial value. (#8564, #9534) - TreeNode: - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX @@ -69,6 +69,10 @@ Other Changes: disabled color buttons have the same color as non-disabled oness. (#9511) - ColorButton: fixed issues/inconsistencies with checkerboard rendering when combining color alpha and global style alpha. (#9511) [@enjmiah, @ocornut] +- Popups: + - Amended context menu right-click opening code to check that the right-click + hasn't been owned by another items. This is more correct and also necessary + to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - Misc: - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] From 9090ba71e53036a306dc3fae375f0ff6203f90c0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 16:38:38 +0200 Subject: [PATCH 205/254] Fixed Tesselation -> Tessellation in comments. --- docs/CHANGELOG.txt | 4 ++-- imgui.cpp | 6 +++--- imgui.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 47d921701..39d871e4c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -42,9 +42,9 @@ HOW TO UPDATE? Breaking Changes: - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in - favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. It is + favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. It is easier to tweak + this allows us to easily scale error threshold on high density screens. - - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). + - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). - Backends: - DirectX12: Removed support for legacy `ImGui_ImplDX12_Init()` signature, which was obsoleted in 1.91.6 (December 2014), because it needed to forcefully disable support diff --git a/imgui.cpp b/imgui.cpp index 3edc0bbc7..2d8e2418f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,8 +395,8 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. - - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. - - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). + - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. + - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). - 2026/07/20 (1.92.9) - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: inputting a value with the keyboard doesn't write intermediate values to backing variable. (#9476) - Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. @@ -1572,7 +1572,7 @@ ImGuiStyle::ImGuiStyle() DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. - // Rendering & Tesselation + // Rendering & Tessellation AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). diff --git a/imgui.h b/imgui.h index 770c14a44..5fa9ca947 100644 --- a/imgui.h +++ b/imgui.h @@ -2384,7 +2384,7 @@ struct ImGuiStyle ImVec2 DisplaySafeAreaPadding; // Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured). float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. - // Rendering & Tesselation + // Rendering & Tessellation bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). From 04b37d6d23de8583bf9efb9c13d3d0cd9ba5bcf0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 18:40:01 +0200 Subject: [PATCH 206/254] Added ImGuiItemFlags_MixedValue support for Drags, Sliders, InputScalar, InputText, RadioButton, Combo. (#5518, #5677, #6865) --- docs/CHANGELOG.txt | 5 ++++ imgui.cpp | 1 + imgui.h | 3 ++- imgui_demo.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++++- imgui_internal.h | 2 +- imgui_widgets.cpp | 38 +++++++++++++++++++--------- 6 files changed, 97 insertions(+), 15 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 39d871e4c..5008da1d3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -74,6 +74,9 @@ Other Changes: hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - Misc: + - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values + in Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), Combo() functions. + The scoped flag is designed for usage by advanced property editors. (#5518, #5677, #6865) - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] - Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC) @@ -90,6 +93,8 @@ Other Changes: - Reworked assert in PushTexture() to allow convenience grace period of using a texture that was queue for destroy during the frame. Make it safe to stick to an existing texture during the frame. (#9528, #8465) +- Demo: + - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without diff --git a/imgui.cpp b/imgui.cpp index 2d8e2418f..2ef75ef1f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4380,6 +4380,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + MixedValueLabel = "-"; InputTextPasswordFontBackupFlags = ImFontFlags_None; InputTextReactivateId = 0; TempInputId = 0; diff --git a/imgui.h b/imgui.h index 5fa9ca947..1329bad3e 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.93.0 WIP" -#define IMGUI_VERSION_NUM 19295 +#define IMGUI_VERSION_NUM 19296 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1258,6 +1258,7 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). + ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. Only supported by some widgets: Checkbox, RadioButton, Sliders and Drags. //--------------------------------------------------------------------------------- // LiveEdit refers to applying edits to backing variables _while_ typing a value using the keyboard. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 314488d94..3c76b8d84 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -87,6 +87,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsListBoxes() // [SECTION] DemoWindowWidgetsLiveEdit() +// [SECTION] DemoWindowWidgetsMixedValues() // [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsPlotting() // [SECTION] DemoWindowWidgetsProgressBars() @@ -2020,7 +2021,7 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) { if (ImGui::TreeNode("Live Edit Flags")) { - IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs"); + IMGUI_DEMO_MARKER("Widgets/Live Edit Flags"); ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing."); @@ -2050,6 +2051,59 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) } } +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgetsMixedValues() +//----------------------------------------------------------------------------- + +static void DemoWindowWidgetsMixedValues() +{ + if (ImGui::TreeNode("Mixed Values")) + { + // This is designed for advanced property editors which are generally reusable and data-driven. + HelpMarker("Using ImGuiItemFlags_MixedValue."); + + static float items[3] = { 12.0f, 0.0f, 0.0f }; + float* item_ref = &items[0]; + + ImGui::SeparatorText("Scalar/Text Widgets"); + const bool is_mixed = memcmp(&items[0], &items[1], sizeof(float)) != 0 || memcmp(&items[0], &items[2], sizeof(float)) != 0; + + // Demonstrate Drags, Sliders, Inputs + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, is_mixed); + bool edited = false; + edited |= ImGui::DragFloat("DragFloat", item_ref); + edited |= ImGui::SliderFloat("SliderFloat", item_ref, 0.0f, 100.0f); + edited |= ImGui::InputFloat("InputFloat", item_ref, 1.0f); + if (edited) + for (float& item : items) + if (&item != item_ref) + item = *item_ref; + ImGui::PopItemFlag(); + + ImGui::Text("Underlying data:"); + ImGui::InputFloat("item 0 (ref)", &items[0]); + ImGui::InputFloat("item 1", &items[1]); + ImGui::InputFloat("item 2", &items[2]); + + // Demonstrate Checkbox() + // (this is automatically used by e.g. CheckboxFlags()) + ImGui::SeparatorText("Others Widgets"); + bool b_on = true, b_off = false; + ImGui::Checkbox("Checkbox On", &b_on); + ImGui::Checkbox("Checkbox Off", &b_off); + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); + ImGui::Checkbox("Checkbox Mixed", &b_off); + ImGui::RadioButton("RadioButton Mixed", true); + ImGui::SameLine(); + ImGui::RadioButton("RadioButton Mixed##2", true); // Showing 2 radio buttons makes the example more clear + int combo_idx = 0; + ImGui::Combo("Combo", &combo_idx, "One\0Two\0Three\0"); + ImGui::PopItemFlag(); + + ImGui::TreePop(); + } +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsMultiComponents() //----------------------------------------------------------------------------- @@ -2230,12 +2284,14 @@ static void DemoWindowWidgetsQueryingStatuses() }; static int item_type = 4; static bool item_disabled = false; + static bool item_mixedvalue = false; static bool liveedit_flags_override = false; static ImGuiItemFlags liveedit_flags = 0; ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); + ImGui::Checkbox("Item MixedValue", &item_mixedvalue); ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override); ImGui::SameLine(); if (!liveedit_flags_override) @@ -2260,6 +2316,8 @@ static void DemoWindowWidgetsQueryingStatuses() static char str[16] = {}; if (item_disabled) ImGui::BeginDisabled(true); + if (item_mixedvalue) + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) @@ -2343,6 +2401,8 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::PopItemFlag(); ImGui::PopItemFlag(); } + if (item_mixedvalue) + ImGui::PopItemFlag(); if (item_disabled) ImGui::EndDisabled(); @@ -4505,6 +4565,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsImages(); DemoWindowWidgetsListBoxes(); DemoWindowWidgetsLiveEdit(demo_data); + DemoWindowWidgetsMixedValues(); DemoWindowWidgetsMultiComponents(); DemoWindowWidgetsPlotting(); DemoWindowWidgetsProgressBars(); diff --git a/imgui_internal.h b/imgui_internal.h index 19d7043b8..ca9ef8266 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -993,7 +993,6 @@ enum ImGuiItemFlagsPrivate_ { // Controlled by user ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. - ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). @@ -2305,6 +2304,7 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information + const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfoId; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 84718fb1f..fd433914c 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1398,7 +1398,7 @@ bool ImGui::RadioButton(const char* label, bool active) RenderNavCursor(total_bb, id); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); - if (active) + if (active && (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) == 0) { const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); @@ -2176,7 +2176,9 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo // Call the getter to obtain the preview string which is a parameter to BeginCombo() const char* preview_value = NULL; - if (*current_item >= 0 && *current_item < items_count) + if ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_MixedValue) + preview_value = ""; + else if (*current_item >= 0 && *current_item < items_count) preview_value = getter(user_data, *current_item); // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. @@ -2815,8 +2817,9 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, MarkItemEdited(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); @@ -3422,8 +3425,9 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); @@ -3576,8 +3580,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding + const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3815,7 +3820,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG // Only mark as edited if new value is different g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; - bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0 || (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue); if (value_changed) MarkItemEdited(id); return value_changed; @@ -3892,6 +3897,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); } } + if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) + value_changed |= ret; // Step buttons if (has_step_buttons) @@ -4857,6 +4864,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; if (is_resizable) IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! @@ -5021,7 +5029,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Select the buffer to render. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; - bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed; // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) @@ -5448,7 +5456,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified - if (strcmp(state->TextSrc, buf) != 0) + if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)) { apply_new_text = state->TextSrc; apply_new_text_length = state->TextLen; @@ -5458,7 +5466,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { // Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert) - value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0); + value_changed = (validated || clear_active_id || revert_edit) && (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)); apply_new_text = value_changed ? state->TextSrc : NULL; apply_new_text_length = value_changed ? state->TextLen : 0; } @@ -5543,7 +5551,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Display hint when contents is empty // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368) - const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed; if (new_is_displaying_hint != is_displaying_hint) { if (is_password && !is_displaying_hint) @@ -5552,10 +5560,16 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); } - if (is_displaying_hint) + if (is_mixed && g.ActiveId != id && apply_new_text == NULL) + { + buf_display = g.MixedValueLabel; + buf_display_end = buf_display + strlen(g.MixedValueLabel); + render_cursor = render_selection = false; + } + else if (is_displaying_hint) { buf_display = hint; - buf_display_end = hint + ImStrlen(hint); + buf_display_end = buf_display + ImStrlen(buf_display); } else { From dd32dace81765542d42131d80c043155c16358ac Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 19:09:24 +0200 Subject: [PATCH 207/254] InputText: using MixedValue automatically sets ImGuiInputTextFlags_AutoSelectAll. (#5518, #5677, #6865) --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index fd433914c..591750e5d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4951,7 +4951,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_multiline) { - if (flags & ImGuiInputTextFlags_AutoSelectAll) + if ((flags & ImGuiInputTextFlags_AutoSelectAll) || is_mixed) select_all = true; if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState))) select_all = true; From 334f484892a1fa881d2a927c2aff222c15458b8f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 7 Sep 2026 19:43:18 +0200 Subject: [PATCH 208/254] ColorEdit4, ColorPicker4: added support for ImGuiItemFlags_MixedValue. (#5518, #5677, #6865) --- docs/CHANGELOG.txt | 5 +++-- imgui_demo.cpp | 6 ++++-- imgui_widgets.cpp | 22 ++++++++++++++++++---- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5008da1d3..a460b728d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -74,8 +74,9 @@ Other Changes: hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - Misc: - - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values - in Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), Combo() functions. + - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values. + Supported by: Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), ColorXXX() + and Combo() functions. The scoped flag is designed for usage by advanced property editors. (#5518, #5677, #6865) - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3c76b8d84..d75566170 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2085,9 +2085,9 @@ static void DemoWindowWidgetsMixedValues() ImGui::InputFloat("item 1", &items[1]); ImGui::InputFloat("item 2", &items[2]); - // Demonstrate Checkbox() - // (this is automatically used by e.g. CheckboxFlags()) + // Demonstrate Checkbox(), RadioButton(), Combo(), ColorEdit4() ImGui::SeparatorText("Others Widgets"); + ImGui::Text("(note: edits are not applied in this demo)"); // <-- Would need more state tracking. bool b_on = true, b_off = false; ImGui::Checkbox("Checkbox On", &b_on); ImGui::Checkbox("Checkbox Off", &b_off); @@ -2098,6 +2098,8 @@ static void DemoWindowWidgetsMixedValues() ImGui::RadioButton("RadioButton Mixed##2", true); // Showing 2 radio buttons makes the example more clear int combo_idx = 0; ImGui::Combo("Combo", &combo_idx, "One\0Two\0Three\0"); + ImVec4 color(0.5f, 0.5f, 0.5f, 0.5f); + ImGui::ColorEdit4("ColorEdit4", &color.x); ImGui::PopItemFlag(); ImGui::TreePop(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 591750e5d..706849773 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6058,7 +6058,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + PushItemFlag(ImGuiItemFlags_MixedValue, false); value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemFlag(); } EndPopup(); } @@ -6333,6 +6335,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + PushItemFlag(ImGuiItemFlags_MixedValue, false); ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); @@ -6350,6 +6353,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } } PopItemFlag(); + PopItemFlag(); EndGroup(); } @@ -6568,14 +6572,20 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (flags & ImGuiColorEditFlags_InputHSV) ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); - ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); - float grid_step = ImMin(size.x, size.y) / 2.99f; - float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + const ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); + const float grid_step = ImMin(size.x, size.y) / 2.99f; + const float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; const float backup_alpha = g.Style.Alpha; if (g.DisabledStackSize > 0) g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. - if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) + if (is_mixed) + { + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); + RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + } + else if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb.Min.x + bb.Max.x) * 0.5f); if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) @@ -6612,7 +6622,9 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); else SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); + PushItemFlag(ImGuiItemFlags_MixedValue, false); ColorButton(desc_id, col, flags); + PopItemFlag(); SameLine(); TextEx("Color"); EndDragDropSource(); @@ -6656,7 +6668,9 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_; + PushItemFlag(ImGuiItemFlags_MixedValue, false); ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz); + PopItemFlag(); SameLine(); if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) { From 148d128f6e25cb7ce7d94294cdeb57173218cd18 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 16:46:08 +0200 Subject: [PATCH 209/254] Mixed Value: use ImGuiCol_TextMixedValue (= ImGuiCol_TextDisabled). Set MixedValueLabel to null to display original value. (#5518, #5677, #6865) --- imgui.cpp | 1 + imgui_internal.h | 4 +++- imgui_widgets.cpp | 35 ++++++++++++++++++++++++++--------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2ef75ef1f..105b6c2cf 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3945,6 +3945,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end // Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take // better advantage of the render function taking size into account for coarse clipping. void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +// DOES NOT CALL LogRenderedText(), unlike RenderTextClipped!!! { // Perform CPU side clipping for single clipped element to avoid using scissor state ImVec2 pos = pos_min; diff --git a/imgui_internal.h b/imgui_internal.h index ca9ef8266..44e78f623 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -932,6 +932,8 @@ struct ImFontStackData // [SECTION] Style support //----------------------------------------------------------------------------- +#define ImGuiCol_TextMixedValue ImGuiCol_TextDisabled + struct ImGuiStyleVarInfo { ImU32 Count : 8; // 1+ @@ -2304,7 +2306,7 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information - const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. + const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. Set to NULL to display original value. ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfoId; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 706849773..1cc5eb2b3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2817,12 +2817,16 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, MarkItemEdited(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (is_mixed) + PopStyleColor(); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3425,12 +3429,16 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (is_mixed) + PopStyleColor(); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3580,10 +3588,15 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding - const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); + if (is_mixed) + PopStyleColor(); + if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -5560,7 +5573,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); } - if (is_mixed && g.ActiveId != id && apply_new_text == NULL) + const bool is_displaying_mixed = is_mixed && g.ActiveId != id && apply_new_text == NULL; + if (is_displaying_mixed && g.MixedValueLabel != NULL) { buf_display = g.MixedValueLabel; buf_display_end = buf_display + strlen(g.MixedValueLabel); @@ -5609,7 +5623,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 draw_scroll; // Render text. We currently only render selection when the widget is active or while scrolling. - const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + const ImU32 text_col = GetColorU32(is_displaying_mixed ? ImGuiCol_TextMixedValue : is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); if (render_cursor || render_selection) { // Render text (with cursor and selection) @@ -6583,7 +6597,10 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (is_mixed) { window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); - RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); + if (g.MixedValueLabel != NULL) + RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + PopStyleColor(); } else if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { From 32c3f6095a90d7f45f6a6a7ef43ebee0239490df Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 17:33:42 +0200 Subject: [PATCH 210/254] BeginComboPreview: set/restore WorkRect.Max.x. (#1658, #4168) --- imgui_internal.h | 2 ++ imgui_widgets.cpp | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 44e78f623..083bc01d6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1192,6 +1192,8 @@ struct IMGUI_API ImGuiComboPreviewData ImVec2 BackupCursorMaxPos; ImVec2 BackupCursorPosPrevLine; float BackupPrevLineTextBaseOffset; + float BackupWorkRectMaxX; + float BackupContentRectMaxX; ImGuiLayoutType BackupLayout; ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1cc5eb2b3..ea62d2f71 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2093,8 +2093,10 @@ void ImGui::EndCombo() EndPopup(); } -// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements -// (Experimental, see GitHub issues: #1658, #4168) +// Call directly after the BeginCombo() call. If you use nested combo, make sure you end wait for EndCombo() to call this! +// The preview is designed to only host non-interactive elements. +// - [BETA] See GitHub issues: #1658, #4168. +// - Not compatible with ImGuiComboFlags_WidthFitPreview. bool ImGui::BeginComboPreview() { ImGuiContext& g = *GImGui; @@ -2113,6 +2115,9 @@ bool ImGui::BeginComboPreview() preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; preview_data->BackupLayout = window->DC.LayoutType; + preview_data->BackupWorkRectMaxX = window->WorkRect.Max.x; + preview_data->BackupContentRectMaxX = window->ContentRegionRect.Max.x; + window->WorkRect.Max.x = window->ContentRegionRect.Max.x = preview_data->PreviewRect.Max.x - g.Style.FramePadding.x; window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; window->DC.CursorMaxPos = window->DC.CursorPos; window->DC.LayoutType = ImGuiLayoutType_Horizontal; @@ -2138,9 +2143,11 @@ void ImGui::EndComboPreview() } PopClipRect(); window->DC.CursorPos = preview_data->BackupCursorPos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); // No need to do the same with IdealMaxPos window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; + window->WorkRect.Max.x = preview_data->BackupWorkRectMaxX; + window->ContentRegionRect.Max.x = preview_data->BackupContentRectMaxX; window->DC.LayoutType = preview_data->BackupLayout; window->DC.IsSameLine = false; preview_data->PreviewRect = ImRect(); From db5b735a5d1c9f766d2df43e419682ecffd8c3fa Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 17:52:28 +0200 Subject: [PATCH 211/254] (Breaking) BeginComboPreview: removed ImGuiComboFlags_CustomPreview: now unnecessary + better detect issues (#1658, #4168) --- imgui_internal.h | 15 +++++++-------- imgui_widgets.cpp | 29 +++++++++++++++-------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 083bc01d6..6877b04ce 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1084,12 +1084,6 @@ enum ImGuiButtonFlagsPrivate_ //ImGuiButtonFlags_NoKeyModifiers = ImGuiButtonFlags_NoKeyModsAllowed, // Renamed in 1.91.4 }; -// Extend ImGuiComboFlags_ -enum ImGuiComboFlagsPrivate_ -{ - ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() -}; - // Extend ImGuiSliderFlags_ enum ImGuiSliderFlagsPrivate_ { @@ -1184,6 +1178,10 @@ enum ImGuiPlotType ImGuiPlotType_Histogram, }; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +//ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() // [Obsoleted in 1.93.0] Unnecessary: can now use BeginComboPreview() without a flag. +#endif + // Storage data for BeginComboPreview()/EndComboPreview() struct IMGUI_API ImGuiComboPreviewData { @@ -1194,7 +1192,8 @@ struct IMGUI_API ImGuiComboPreviewData float BackupPrevLineTextBaseOffset; float BackupWorkRectMaxX; float BackupContentRectMaxX; - ImGuiLayoutType BackupLayout; + ImGuiLayoutType BackupLayout : 8; + int WithinPreview : 2; ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); } }; @@ -3539,7 +3538,7 @@ namespace ImGui // Combos IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); - IMGUI_API bool BeginComboPreview(); + IMGUI_API bool BeginComboPreview(); // Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. IMGUI_API void EndComboPreview(); // Keyboard/Gamepad Navigation diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ea62d2f71..643462278 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1951,7 +1951,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF const ImGuiID id = window->GetID(label); IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together if (flags & ImGuiComboFlags_WidthFitPreview) - IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + IM_ASSERT((flags & ImGuiComboFlags_NoPreview) == 0); const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); const char* label_end = FindRenderedTextEnd(label); @@ -1991,13 +1991,8 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF } RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); - // Custom preview - if (flags & ImGuiComboFlags_CustomPreview) - { - g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); - IM_ASSERT(preview_value == NULL || preview_value[0] == 0); - preview_value = NULL; - } + // Store geometry for BeginComboPreview() - only necessary when visible. + g.ComboPreviewData.PreviewRect = (flags & ImGuiComboFlags_NoPreview) ? ImRect() : ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); // Render preview and label if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) @@ -2093,21 +2088,26 @@ void ImGui::EndCombo() EndPopup(); } -// Call directly after the BeginCombo() call. If you use nested combo, make sure you end wait for EndCombo() to call this! -// The preview is designed to only host non-interactive elements. +// Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. // - [BETA] See GitHub issues: #1658, #4168. +// - The preview is designed to only host non-interactive elements. +// - If you use nested combos, make sure you call this right after BeginCombo() and not after EndCombo(), in order to target the correct one. // - Not compatible with ImGuiComboFlags_WidthFitPreview. +// - 2026-09-08 (1.93.0): removed ImGuiComboFlags_CustomPreview. You can use BeginComboPreview()/EndComboPreview() without an extra flag. bool ImGui::BeginComboPreview() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) return false; - IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? - if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional) + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + if (!window->ClipRect.Overlaps(preview_data->PreviewRect) || preview_data->PreviewRect.GetWidth() <= 0.0f) // Narrower test (optional) + handle _NoPreview return false; + IM_ASSERT_USER_ERROR_RETV(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y, false, + "Call BeginComboPreview() after BeginCombo(), not after EndCombo()!"); // Calling after EndCombo() works only if you don't nest combos. + IM_ASSERT_USER_ERROR_RETV(preview_data->WithinPreview == false, false, + "Cannot recurse BeginComboPreview(): call EndComboPreview() before opening another combo."); // FIXME: This could be contained in a PushWorkRect() api preview_data->BackupCursorPos = window->DC.CursorPos; @@ -2115,6 +2115,7 @@ bool ImGui::BeginComboPreview() preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; preview_data->BackupLayout = window->DC.LayoutType; + preview_data->WithinPreview = true; preview_data->BackupWorkRectMaxX = window->WorkRect.Max.x; preview_data->BackupContentRectMaxX = window->ContentRegionRect.Max.x; window->WorkRect.Max.x = window->ContentRegionRect.Max.x = preview_data->PreviewRect.Max.x - g.Style.FramePadding.x; @@ -2150,7 +2151,7 @@ void ImGui::EndComboPreview() window->ContentRegionRect.Max.x = preview_data->BackupContentRectMaxX; window->DC.LayoutType = preview_data->BackupLayout; window->DC.IsSameLine = false; - preview_data->PreviewRect = ImRect(); + preview_data->WithinPreview = false; } // Getter for the old Combo() API: const char*[] From ea6d21687bec144dd7aee0f4db37f7c61a8799bb Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 21:52:23 +0200 Subject: [PATCH 212/254] BeginComboPreview(): fixed issues with nesting. Previous comments were wrong. (#1658, #4168) --- imgui_internal.h | 6 ++++-- imgui_widgets.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 6877b04ce..35716ba7c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -189,6 +189,7 @@ struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aim struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) +struct ImGuiWindowStackData; // Storage for each window pushed into the stack // Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. @@ -1459,7 +1460,7 @@ struct IMGUI_API ImGuiErrorRecoveryState ImGuiErrorRecoveryState() { memset((void*)this, 0, sizeof(*this)); } }; -// Data saved for each window pushed into the stack +// Storage for each window pushed into the stack. struct ImGuiWindowStackData { ImGuiWindow* Window; @@ -1467,6 +1468,7 @@ struct ImGuiWindowStackData ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting bool DisabledOverrideReenable; // Non-child window override disabled flag float DisabledOverrideReenableAlphaBackup; + ImRect ParentLastComboPreviewRect; }; struct ImGuiShrinkWidthItem @@ -3538,7 +3540,7 @@ namespace ImGui // Combos IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); - IMGUI_API bool BeginComboPreview(); // Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. + IMGUI_API bool BeginComboPreview(); // Submit preview contents a combo. Call this after EndCombo() to display contents that's more than just a text label. IMGUI_API void EndComboPreview(); // Keyboard/Gamepad Navigation diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 643462278..f42b6482f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2074,6 +2074,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags return false; } g.BeginComboDepth++; + g.CurrentWindowStack.back().ParentLastComboPreviewRect = g.ComboPreviewData.PreviewRect; return true; } @@ -2081,6 +2082,7 @@ void ImGui::EndCombo() { ImGuiContext& g = *GImGui; g.BeginComboDepth--; + g.ComboPreviewData.PreviewRect = g.CurrentWindowStack.back().ParentLastComboPreviewRect; char name[16]; ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // FIXME: Move those to helpers? if (strcmp(g.CurrentWindow->Name, name) != 0) @@ -2088,10 +2090,10 @@ void ImGui::EndCombo() EndPopup(); } -// Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. +// Submit preview contents for BeginCombo()/EndCombo(), to display contents that's more than just a text label. // - [BETA] See GitHub issues: #1658, #4168. +// - Make sure you call this after EndCombo(). // - The preview is designed to only host non-interactive elements. -// - If you use nested combos, make sure you call this right after BeginCombo() and not after EndCombo(), in order to target the correct one. // - Not compatible with ImGuiComboFlags_WidthFitPreview. // - 2026-09-08 (1.93.0): removed ImGuiComboFlags_CustomPreview. You can use BeginComboPreview()/EndComboPreview() without an extra flag. bool ImGui::BeginComboPreview() From e8602501bb900fc7c965e65b88fef30b2c80aff1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 9 Sep 2026 20:44:34 +0200 Subject: [PATCH 213/254] Mixed Value: various fixes. (#5518, #5677, #6865) - With MixedValue+LiveEdit: a single edit that ends up with same output reports as edited. - Standardize path sin TempInputscalar(), InputScalar(), InputTextEx(). - Refer to changes in imgui_test_suite, in particular "widgets_datatype_1", "widgets_status_liveedit_off", --- imgui.h | 14 ++++++++++++-- imgui_demo.cpp | 4 ++++ imgui_internal.h | 3 ++- imgui_widgets.cpp | 15 +++++++++++---- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index 1329bad3e..fe4f82d58 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.93.0 WIP" -#define IMGUI_VERSION_NUM 19296 +#define IMGUI_VERSION_NUM 19297 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1258,7 +1258,6 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). - ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. Only supported by some widgets: Checkbox, RadioButton, Sliders and Drags. //--------------------------------------------------------------------------------- // LiveEdit refers to applying edits to backing variables _while_ typing a value using the keyboard. @@ -1272,6 +1271,17 @@ enum ImGuiItemFlags_ ImGuiItemFlags_LiveEditOnInputText = 1 << 7, // true // InputText: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // false // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInput = ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, + + //--------------------------------------------------------------------------------- + // [BETA] MixedValue mode used to represent a mixed/indeterminate state, typically for multi-selection. + // - Replace value display with "-" or a custom label. + // - Enter key validation apply an edit and return true even if value hasn't changed (in order to apply to all). + // - Supported by selected widgets: Checkbox, RadioButton, Sliders, Drags, Inputs, Combo. + // - Note: InputText-side Undo cannot be reliably combined with MixedValue + LiveEdit On: + // - Both the initial edit and subsequent undo/revert will typically make your app code write to all backing objects. + // - If you use MixedMode and the simplest solution is to ensure LiveEdit is off but widgets where this applies. + //--------------------------------------------------------------------------------- + ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. }; // Flags for ImGui::InputText() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d75566170..7e0283bc2 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2062,13 +2062,16 @@ static void DemoWindowWidgetsMixedValues() // This is designed for advanced property editors which are generally reusable and data-driven. HelpMarker("Using ImGuiItemFlags_MixedValue."); + static bool use_liveedit = false; static float items[3] = { 12.0f, 0.0f, 0.0f }; float* item_ref = &items[0]; + ImGui::Checkbox("ImGuiItemFlags_LiveEditOnInput", &use_liveedit); ImGui::SeparatorText("Scalar/Text Widgets"); const bool is_mixed = memcmp(&items[0], &items[1], sizeof(float)) != 0 || memcmp(&items[0], &items[2], sizeof(float)) != 0; // Demonstrate Drags, Sliders, Inputs + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInput, use_liveedit); ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, is_mixed); bool edited = false; edited |= ImGui::DragFloat("DragFloat", item_ref); @@ -2084,6 +2087,7 @@ static void DemoWindowWidgetsMixedValues() ImGui::InputFloat("item 0 (ref)", &items[0]); ImGui::InputFloat("item 1", &items[1]); ImGui::InputFloat("item 2", &items[2]); + ImGui::PopItemFlag(); // Demonstrate Checkbox(), RadioButton(), Combo(), ColorEdit4() ImGui::SeparatorText("Others Widgets"); diff --git a/imgui_internal.h b/imgui_internal.h index 35716ba7c..924da068d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1031,7 +1031,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. ImGuiItemStatusFlags_EditedInternal = 1 << 11, // Similar to ImGuiItemStatusFlags_Edited but bypassing ImGuiItemFlags_NoMarkEdited. - + // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE ImGuiItemStatusFlags_Openable = 1 << 20, // Item is an openable (e.g. TreeNode) @@ -1283,6 +1283,7 @@ struct IMGUI_API ImGuiInputTextState bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool EditedBefore; // edited since activated bool EditedThisFrame; // edited this frame + bool ValidatedThisFrame; bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. ImS8 LastMoveDirectionLR; // ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s int ReloadSelectionStart; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f42b6482f..8a1637f44 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3843,7 +3843,11 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG // Only mark as edited if new value is different g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; - bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0 || (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue); + bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) + if (ImGuiInputTextState* state = GetInputTextState(g.LastItemData.ID)) + value_changed |= (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (state->EditedThisFrame | state->ValidatedThisFrame) : state->ValidatedThisFrame; + if (value_changed) MarkItemEdited(id); return value_changed; @@ -3921,7 +3925,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data } } if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) - value_changed |= ret; + if (ImGuiInputTextState* state = GetInputTextState(g.LastItemData.ID)) + value_changed |= (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (state->EditedThisFrame | state->ValidatedThisFrame) : state->ValidatedThisFrame; // Step buttons if (has_step_buttons) @@ -5061,6 +5066,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (state != NULL && state->ID == id) { state->Flags = flags; + state->EditedThisFrame = state->ValidatedThisFrame = false; //state->LastFrameActive = g.FrameCount; // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset) @@ -5076,7 +5082,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { IM_ASSERT(state != NULL); - state->EditedThisFrame = false; state->BufCapacity = buf_size; state->WrapWidth = wrap_width; @@ -5479,7 +5484,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified - if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)) + if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && (state->EditedThisFrame || validated))) { apply_new_text = state->TextSrc; apply_new_text_length = state->TextLen; @@ -5806,6 +5811,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); + if (state && validated) + state->ValidatedThisFrame = true; if (value_changed) MarkItemEdited(id); From e0a2f6dea98f76a793a6e62a2bdb7ee9b29f5add Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 10 Sep 2026 18:32:44 +0200 Subject: [PATCH 214/254] Drag and Drop: minor refactor into a helper RenderDragDropTargetRectForViewport() function. --- imgui.cpp | 19 ++++++++++++------- imgui_internal.h | 3 ++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 105b6c2cf..90a80a3f8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15357,15 +15357,9 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame) const bool draw_target_rect = payload.Preview && !(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); if (draw_target_rect && g.DragDropTargetFullViewport != 0) - { - ImRect bb = g.DragDropTargetRect; - bb.Expand(-3.5f); - RenderDragDropTargetRectEx(GetForegroundDrawList(), bb, g.Style.DragDropTargetRounding); - } + RenderDragDropTargetRectForViewport(g.DragDropTargetFullViewport, g.DragDropTargetRect); else if (draw_target_rect) - { RenderDragDropTargetRectForItem(r); - } g.DragDropAcceptFrameCount = g.FrameCount; if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1) @@ -15396,6 +15390,17 @@ void ImGui::RenderDragDropTargetRectForItem(const ImRect& bb) window->DrawList->PopClipRect(); } +void ImGui::RenderDragDropTargetRectForViewport(ImGuiID viewport_id, const ImRect& bb) +{ + IM_ASSERT(viewport_id != 0); + IM_UNUSED(viewport_id); // Unused in this branch + ImGuiContext& g = *GImGui; + ImGuiViewport* viewport = g.Viewports[0]; + ImRect bb_padded = bb; + bb_padded.Expand(-g.Style.DragDropTargetPadding); + RenderDragDropTargetRectEx(GetForegroundDrawList(viewport), bb_padded, g.Style.DragDropTargetRounding); +} + void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding) { ImGuiContext& g = *GImGui; diff --git a/imgui_internal.h b/imgui_internal.h index 924da068d..bac5b3355 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3354,7 +3354,7 @@ namespace ImGui // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. IMGUI_API ImGuiIO& GetIO(ImGuiContext* ctx); IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx); - inline float GetScale() { ImGuiContext& g = *GImGui; return g.Style._MainScale; } // FIXME-DPI: I don't want to formalize this just yet. Because reasons. Please don't use. + inline float GetScale() { ImGuiContext& g = *GImGui; return g.Style._MainScale; } // FIXME-DPI: PLEASE DO NOT USE. I don't want to formalize this just yet. Because reasons. inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); @@ -3674,6 +3674,7 @@ namespace ImGui IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); IMGUI_API void RenderDragDropTargetRectForItem(const ImRect& bb); + IMGUI_API void RenderDragDropTargetRectForViewport(ImGuiID viewport_id, const ImRect& bb); IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb, float rounding); // Typing-Select API From ea60e7b7488b473c61d0cdc47b7fc83dd9e7ee4e Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 14 Sep 2026 15:56:16 +0200 Subject: [PATCH 215/254] TabBar: added TabBarGetTabPos() helper. # Conflicts: # imgui_internal.h # imgui_widgets.cpp --- imgui_internal.h | 1 + imgui_widgets.cpp | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index bac5b3355..a248fc800 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3707,6 +3707,7 @@ namespace ImGui IMGUI_API ImGuiTabItem* TabBarGetCurrentTab(ImGuiTabBar* tab_bar); inline int TabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { return tab_bar->Tabs.index_from_ptr(tab); } IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API ImVec2 TabBarGetTabPos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); IMGUI_API void TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8a1637f44..62b682adb 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -10402,6 +10402,14 @@ const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) return tab_bar->TabsNames.Buf.Data + tab->NameOffset; } +ImVec2 ImGui::TabBarGetTabPos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + if ((tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0) + return tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + else + return tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); +} + // The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { @@ -10855,10 +10863,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Layout const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; size.x = tab->Width; - if (is_central_section) - window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f); - else - window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); + window->DC.CursorPos = TabBarGetTabPos(tab_bar, tab); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + size); From c8b9f0d6be440f6588e96ceaa1bc4dcbb4ed1861 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 14 Sep 2026 19:04:23 +0200 Subject: [PATCH 216/254] TabBar: Improved scroll target when clicking a Tab that is larger than the scrolling region width. The margin that is useful to indicate other tabs being available gets in the way of visibility in this case. --- docs/CHANGELOG.txt | 3 +++ imgui_demo.cpp | 2 +- imgui_widgets.cpp | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a460b728d..d68f0c54b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,6 +60,9 @@ Other Changes: - TreeNode: - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX features or other features requiring stack storage. (#9509) [@lasrod] +- TabBar: + - Improved scroll target when clicking a Tab that is larger than the scrolling + region width. - Fonts: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7e0283bc2..ea7a22741 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3748,7 +3748,7 @@ static void DemoWindowWidgetsTabs() ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyShrink; + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyMixed; EditTabBarFittingPolicyFlags(&tab_bar_flags); if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 62b682adb..8d2714772 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -10460,8 +10460,10 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui if (tab->Flags & ImGuiTabItemFlags_SectionMask_) return; + // When scrolling to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + // Disable the margin if the scrolling section is too small for the target tab: prefer displaying a maximum of the label. ImGuiContext& g = *GImGui; - float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + float margin = ImClamp(tab_bar->ScrollingRectMaxX - tab_bar->ScrollingRectMinX - tab->Width, g.Style.ItemInnerSpacing.x, g.FontSize * 1.0f); int order = TabBarGetTabOrder(tab_bar, tab); // Scrolling happens only in the central section (leading/trailing sections are not scrolling) From b00dd14a347c7c5ca59a1e4dd50346d67a29cb0a Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 14 Sep 2026 19:25:33 +0200 Subject: [PATCH 217/254] TabBar: using <> scrolling buttons while the selected Tab is at either end of the section mid-section but not fully visible always makes it fully visible. --- docs/CHANGELOG.txt | 5 ++++- imgui_demo.cpp | 11 +++++++++++ imgui_widgets.cpp | 14 +++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d68f0c54b..96d6cd99b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -62,7 +62,10 @@ Other Changes: features or other features requiring stack storage. (#9509) [@lasrod] - TabBar: - Improved scroll target when clicking a Tab that is larger than the scrolling - region width. + section width. + - Using <> scrolling buttons while the selected Tab is at either end of the + section mid-section but not fully visible always makes it fully visible + (before eventually selecting a Tab in the leading/trailing section) - Fonts: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of diff --git a/imgui_demo.cpp b/imgui_demo.cpp index ea7a22741..7478a99ee 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3744,8 +3744,10 @@ static void DemoWindowWidgetsTabs() // but they tend to make more sense together) static bool show_leading_button = true; static bool show_trailing_button = true; + static bool show_leading_trailing_tabs = false; ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); + ImGui::Checkbox("Show Leading+Trailing TabItem()", &show_leading_trailing_tabs); // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyMixed; @@ -3763,6 +3765,15 @@ static void DemoWindowWidgetsTabs() ImGui::EndPopup(); } + // Demo Leading/Trailing Tabs + if (show_leading_trailing_tabs) + { + if (ImGui::BeginTabItem("Leading", NULL, ImGuiTabItemFlags_Leading)) + ImGui::EndTabItem(); + if (ImGui::BeginTabItem("Trailing", NULL, ImGuiTabItemFlags_Trailing)) + ImGui::EndTabItem(); + } + // Demo Trailing Tabs: click the "+" button to add a new tab. // (In your app you may want to use a font icon instead of the "+") // We submit it before the regular tabs, but thanks to the ImGuiTabItemFlags_Trailing flag it will always appear at the end. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8d2714772..bb5a79005 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -10220,16 +10220,17 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } // Horizontal scrolling buttons - // Important: note that TabBarScrollButtons() will alter BarRect.Max.x. + // - Important: note that TabBarScrollButtons() will alter BarRect.Max.x. + // - Selection skips buttons and might cross through sections if there are Tabs in Leading/Trailing sections. const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed); const float width_all_tabs_to_use_for_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) ? tab_bar->WidthAllTabs : width_all_tabs_after_min_width_shrink; tab_bar->ScrollButtonEnabled = ((width_all_tabs_to_use_for_scroll > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && can_scroll); if (tab_bar->ScrollButtonEnabled) if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) { - scroll_to_tab_id = scroll_and_select_tab->ID; if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) - tab_bar->SelectedTabId = scroll_to_tab_id; + tab_bar->SelectedTabId = scroll_and_select_tab->ID; + scroll_to_tab_id = scroll_and_select_tab->ID; } if (scroll_to_tab_id == 0 && scroll_to_selected_tab) scroll_to_tab_id = tab_bar->SelectedTabId; @@ -10457,6 +10458,13 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id); if (tab == NULL) return; + + // Clamp attempt to scroll to leading/trailing sections items. + // This could be done at the TabBarScrollingButtons() call site as well, but here works. + if (tab->Flags & ImGuiTabItemFlags_Leading) + tab = &tab_bar->Tabs[sections[0].TabCount]; + else if (tab->Flags & ImGuiTabItemFlags_Trailing) + tab = &tab_bar->Tabs[sections[0].TabCount + sections[1].TabCount]; if (tab->Flags & ImGuiTabItemFlags_SectionMask_) return; From 8e174685ae0a1f29626dcf746511a110882cd7cc Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 16 Sep 2026 16:14:06 +0200 Subject: [PATCH 218/254] Backends: added ImGui_ImplXXXX_GetRenderState() helpers to all backends not having one. --- backends/imgui_impl_dx10.h | 2 ++ backends/imgui_impl_dx11.h | 2 ++ backends/imgui_impl_dx12.h | 2 ++ backends/imgui_impl_sdlgpu3.h | 2 ++ backends/imgui_impl_sdlrenderer2.h | 2 ++ backends/imgui_impl_sdlrenderer3.h | 2 ++ backends/imgui_impl_vulkan.h | 2 ++ backends/imgui_impl_wgpu.h | 2 ++ 8 files changed, 16 insertions(+) diff --git a/backends/imgui_impl_dx10.h b/backends/imgui_impl_dx10.h index 179756fb0..427d89b95 100644 --- a/backends/imgui_impl_dx10.h +++ b/backends/imgui_impl_dx10.h @@ -46,4 +46,6 @@ struct ImGui_ImplDX10_RenderState //ID3D10SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest) }; +static inline ImGui_ImplDX10_RenderState* ImGui_ImplDX10_GetRenderState() { return (ImGui_ImplDX10_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_dx11.h b/backends/imgui_impl_dx11.h index 56166b92a..482b360a2 100644 --- a/backends/imgui_impl_dx11.h +++ b/backends/imgui_impl_dx11.h @@ -49,4 +49,6 @@ struct ImGui_ImplDX11_RenderState //ID3D11SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest) }; +static inline ImGui_ImplDX11_RenderState* ImGui_ImplDX11_GetRenderState() { return (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index dfb327fe4..fb9085c58 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -75,6 +75,8 @@ struct ImGui_ImplDX12_RenderState ID3D12GraphicsCommandList* CommandList; }; +static inline ImGui_ImplDX12_RenderState* ImGui_ImplDX12_GetRenderState() { return (ImGui_ImplDX12_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy initialization API Obsoleted in 1.91.5 // - font_srv_cpu_desc_handle and font_srv_gpu_desc_handle are handles to a single SRV descriptor to use for the internal font texture, they must be in 'srv_descriptor_heap' diff --git a/backends/imgui_impl_sdlgpu3.h b/backends/imgui_impl_sdlgpu3.h index bc21d1c75..9c7d56e46 100644 --- a/backends/imgui_impl_sdlgpu3.h +++ b/backends/imgui_impl_sdlgpu3.h @@ -59,4 +59,6 @@ struct ImGui_ImplSDLGPU3_RenderState SDL_GPUDevice* Device; }; +static inline ImGui_ImplSDLGPU3_RenderState* ImGui_ImplSDLGPU3_GetRenderState() { return (ImGui_ImplSDLGPU3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlrenderer2.h b/backends/imgui_impl_sdlrenderer2.h index f33bc160e..0c14912db 100644 --- a/backends/imgui_impl_sdlrenderer2.h +++ b/backends/imgui_impl_sdlrenderer2.h @@ -52,4 +52,6 @@ struct ImGui_ImplSDLRenderer2_RenderState SDL_Renderer* Renderer; }; +static inline ImGui_ImplSDLRenderer2_RenderState* ImGui_ImplSDLRenderer2_GetRenderState() { return (ImGui_ImplSDLRenderer2_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index c01a16d48..d5a3c8e5b 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -50,4 +50,6 @@ struct ImGui_ImplSDLRenderer3_RenderState SDL_Renderer* Renderer; }; +static inline ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() { return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index cf6a3be15..646d228c9 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -176,6 +176,8 @@ struct ImGui_ImplVulkan_RenderState VkPipelineLayout PipelineLayout; }; +static inline ImGui_ImplVulkan_RenderState* ImGui_ImplVulkan_GetRenderState() { return (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers //------------------------------------------------------------------------- diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index ed84f5ecb..707c12772 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -82,6 +82,8 @@ struct ImGui_ImplWGPU_RenderState WGPURenderPassEncoder RenderPassEncoder; }; +static inline ImGui_ImplWGPU_RenderState* ImGui_ImplWGPU_GetRenderState() { return (ImGui_ImplWGPU_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } + //------------------------------------------------------------------------- // Internal Helpers // Those are currently used by our example applications. From a5b5f2d85a8779841a2fb181428fe5bd2c4c15d6 Mon Sep 17 00:00:00 2001 From: Clownacy Date: Sun, 13 Sep 2026 13:15:52 +0100 Subject: [PATCH 219/254] Backends: SDLRenderer3: expose CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState. (#9543) --- backends/imgui_impl_sdlrenderer3.cpp | 23 ++++++++++------------- backends/imgui_impl_sdlrenderer3.h | 3 ++- docs/CHANGELOG.txt | 1 + 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index cb975393e..01905202e 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -62,9 +62,6 @@ struct ImGui_ImplSDLRenderer3_Data SDL_Renderer* Renderer; // Main viewport's renderer ImVector ColorBuffer; - // Render State - SDL_ScaleMode CurrentScaleMode; - ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -78,14 +75,14 @@ static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData() // Functions static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer) { - ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); // Clear out any viewports and cliprect set by the user // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process. SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderClipRect(renderer, nullptr); - bd->CurrentScaleMode = SDL_SCALEMODE_LINEAR; + render_state->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } void ImGui_ImplSDLRenderer3_NewFrame() @@ -115,8 +112,8 @@ static int SDL_RenderGeometryRaw8BitColor(SDL_Renderer* renderer, ImVectorCurrentScaleMode = SDL_SCALEMODE_LINEAR; } -static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); bd->CurrentScaleMode = SDL_SCALEMODE_NEAREST; } +static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } +static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_NEAREST; } void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) { @@ -159,19 +156,19 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* SDL_GetRenderViewport(renderer, &old.Viewport); SDL_GetRenderClipRect(renderer, &old.ClipRect); - // Setup desired state - ImGui_ImplSDLRenderer3_SetupRenderState(renderer); - // Setup render state structure (for callbacks and custom texture bindings) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGui_ImplSDLRenderer3_RenderState render_state; render_state.Renderer = renderer; platform_io.Renderer_RenderState = &render_state; + // Setup desired state + ImGui_ImplSDLRenderer3_SetupRenderState(renderer); + // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = render_scale; - SDL_ScaleMode last_scale_mode = bd->CurrentScaleMode; + SDL_ScaleMode last_scale_mode = render_state.CurrentScaleMode; // Render command lists for (const ImDrawList* draw_list : draw_data->CmdLists) @@ -209,12 +206,12 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv)); const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+ - if (last_scale_mode != bd->CurrentScaleMode) + if (last_scale_mode != render_state.CurrentScaleMode) SDL_FlushRenderer(renderer); // Bind texture, Draw SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); - SDL_SetTextureScaleMode(tex, bd->CurrentScaleMode); + SDL_SetTextureScaleMode(tex, render_state.CurrentScaleMode); SDL_RenderGeometryRaw8BitColor(renderer, bd->ColorBuffer, tex, xy, (int)sizeof(ImDrawVert), color, (int)sizeof(ImDrawVert), diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index d5a3c8e5b..7599974d1 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -27,7 +27,7 @@ #include "imgui.h" // IMGUI_IMPL_API #ifndef IMGUI_DISABLE -struct SDL_Renderer; +#include // Follow "Getting Started" link and check examples/ folder to learn about using backends! IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer); @@ -48,6 +48,7 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) struct ImGui_ImplSDLRenderer3_RenderState { SDL_Renderer* Renderer; + SDL_ScaleMode CurrentScaleMode; }; static inline ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() { return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 96d6cd99b..ce018600d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -106,6 +106,7 @@ Other Changes: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without going through a SDL_Renderer. (#5592) [@lailoken] + - SDLRenderer3: expose CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState. (#9543, #9378) [@Clownacy] - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] From 7cb5ded5d2f1b9c7836c62beed79c23af0837f16 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 11:36:33 +0200 Subject: [PATCH 220/254] Backends: SDLRenderer2, SDLRenderer3: reduce includes. (#9543) Amend a5b5f2d --- backends/imgui_impl_sdlrenderer2.cpp | 3 ++- backends/imgui_impl_sdlrenderer3.cpp | 3 ++- backends/imgui_impl_sdlrenderer3.h | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index e295e8677..e66150e57 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -55,7 +55,8 @@ #endif // SDL -#include +#include +#include #if !SDL_VERSION_ATLEAST(2,0,17) #error This backend requires SDL 2.0.17+ because of SDL_RenderGeometry() function #endif diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 01905202e..1a287efa6 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -51,7 +51,8 @@ #endif // SDL -#include +#include +#include #if !SDL_VERSION_ATLEAST(3,0,0) #error This backend requires SDL 3.0.0+ #endif diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index 7599974d1..e4d483ab8 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -27,7 +27,8 @@ #include "imgui.h" // IMGUI_IMPL_API #ifndef IMGUI_DISABLE -#include +// For SDL_ScaleMode which cannot be forward declared. +#include // Follow "Getting Started" link and check examples/ folder to learn about using backends! IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer); From 8d3e37eb5343d4a5aa15718ea0a1889c6e4f2d6c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 14:32:33 +0200 Subject: [PATCH 221/254] Backends: SDLRenderer3: restore modified texture scale modes at the end of RenderDrawData(). (#9378, #9543) --- backends/imgui_impl_sdlrenderer3.cpp | 59 ++++++++++++++++++++++------ backends/imgui_impl_sdlrenderer3.h | 2 +- docs/CHANGELOG.txt | 3 +- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 1a287efa6..3db26573c 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -25,6 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-17: Restore modified texture scale modes at the end of RenderDrawData(). Added CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState (#9543, #9378) // 2026-07-15: Fixed default sampler state to be linear (broken 2026-04-23). (#9470, #9378) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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) @@ -58,10 +59,17 @@ #endif // SDL_Renderer data +struct ImGui_ImplSDLRenderer3_TexScaleModeEntry +{ + SDL_Texture* Texture; + SDL_ScaleMode ScaleModeBackup; +}; + struct ImGui_ImplSDLRenderer3_Data { SDL_Renderer* Renderer; // Main viewport's renderer ImVector ColorBuffer; + ImVector TexScaleModeBackups; // Backup of texture scale modes ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); } }; @@ -82,7 +90,6 @@ static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer) // FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process. SDL_SetRenderViewport(renderer, nullptr); SDL_SetRenderClipRect(renderer, nullptr); - render_state->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } @@ -116,6 +123,38 @@ static void ImGui_ImplSDLRenderer3_DrawCallback_ResetRenderState(const ImDrawLis static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_LINEAR; } static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_NEAREST; } +// SDLRenderer has various design issues which makes it hard to do it compared to regular graphics API. +// - scale/filtering mode is tied to a texture. +// - scale/filtering mode is not queued as part of the SDL_RenderGeometryXXX commands, so a change in one texture requires a flush. +static void ImGui_ImplSDLRenderer3_TexScaleModeUpdate(SDL_Renderer* renderer, SDL_Texture* tex, SDL_ScaleMode desired_scale_mode) +{ + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + SDL_ScaleMode tex_scale_mode = desired_scale_mode; + if (!SDL_GetTextureScaleMode(tex, &tex_scale_mode) || tex_scale_mode == desired_scale_mode) + return; + SDL_FlushRenderer(renderer); + SDL_SetTextureScaleMode(tex, desired_scale_mode); + + // Store backup of old scale mode + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.Texture == tex) + return; + ImGui_ImplSDLRenderer3_TexScaleModeEntry entry = { tex, tex_scale_mode }; + bd->TexScaleModeBackups.push_back(entry); +} + +static void ImGui_ImplSDLRenderer3_TexScaleModeRestoreBackups(SDL_Renderer* renderer) +{ + ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); + if (bd->TexScaleModeBackups.Size == 0) + return; + SDL_FlushRenderer(renderer); + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.ScaleModeBackup != SDL_SCALEMODE_INVALID) + SDL_SetTextureScaleMode(entry.Texture, entry.ScaleModeBackup); + bd->TexScaleModeBackups.resize(0); +} + void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) { ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); @@ -165,11 +204,11 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* // Setup desired state ImGui_ImplSDLRenderer3_SetupRenderState(renderer); + IM_ASSERT(bd->TexScaleModeBackups.Size == 0); // Will project scissor/clipping rectangles into framebuffer space ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_scale = render_scale; - SDL_ScaleMode last_scale_mode = render_state.CurrentScaleMode; // Render command lists for (const ImDrawList* draw_list : draw_data->CmdLists) @@ -199,20 +238,17 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; } if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; - SDL_Rect r = { (int)(clip_min.x), (int)(clip_min.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y) }; SDL_SetRenderClipRect(renderer, &r); + // Setup texture scale/filtering mode + SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); + ImGui_ImplSDLRenderer3_TexScaleModeUpdate(renderer, tex, render_state.CurrentScaleMode); + + // Draw const float* xy = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, pos)); const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv)); - const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+ - - if (last_scale_mode != render_state.CurrentScaleMode) - SDL_FlushRenderer(renderer); - - // Bind texture, Draw - SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); - SDL_SetTextureScaleMode(tex, render_state.CurrentScaleMode); + const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); SDL_RenderGeometryRaw8BitColor(renderer, bd->ColorBuffer, tex, xy, (int)sizeof(ImDrawVert), color, (int)sizeof(ImDrawVert), @@ -227,6 +263,7 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* // Restore modified SDL_Renderer state SDL_SetRenderViewport(renderer, old.ViewportEnabled ? &old.Viewport : nullptr); SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); + ImGui_ImplSDLRenderer3_TexScaleModeRestoreBackups(renderer); } void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index e4d483ab8..5559a5035 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -49,7 +49,7 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) struct ImGui_ImplSDLRenderer3_RenderState { SDL_Renderer* Renderer; - SDL_ScaleMode CurrentScaleMode; + SDL_ScaleMode CurrentScaleMode; // Current scale mode during render. }; static inline ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() { return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ce018600d..6ba8bcfdd 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -93,7 +93,7 @@ Other Changes: and various operators. - IO: restore default clipboard/ime/shell platform handlers on ClearPlatformHandlers(), fixing issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] -- ImDrawList: +- DrawList: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. - Circle and Curves tessellation error are scaled accordingly. @@ -106,6 +106,7 @@ Other Changes: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without going through a SDL_Renderer. (#5592) [@lailoken] + - SDLRenderer3: restore any temporarily modified texture scale modes at the of `RenderDrawData()`. (#9378) - SDLRenderer3: expose CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState. (#9543, #9378) [@Clownacy] - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` From 09b7ff9385c714e177f55df35436193a300652f2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 19:08:01 +0200 Subject: [PATCH 222/254] Backends: rework header comments about ImGui_ImplXXX_GetRenderState() functions. Amend 8e17468 --- backends/imgui_impl_dx10.cpp | 1 + backends/imgui_impl_dx10.h | 1 + backends/imgui_impl_dx11.cpp | 2 +- backends/imgui_impl_dx11.h | 2 +- backends/imgui_impl_dx12.cpp | 2 +- backends/imgui_impl_dx12.h | 2 +- backends/imgui_impl_opengl3.cpp | 1 + backends/imgui_impl_opengl3.h | 1 + backends/imgui_impl_sdlgpu3.cpp | 1 + backends/imgui_impl_sdlgpu3.h | 1 + backends/imgui_impl_sdlrenderer3.cpp | 2 +- backends/imgui_impl_sdlrenderer3.h | 2 +- backends/imgui_impl_vulkan.cpp | 2 +- backends/imgui_impl_vulkan.h | 2 +- backends/imgui_impl_wgpu.cpp | 2 +- backends/imgui_impl_wgpu.h | 2 +- 16 files changed, 16 insertions(+), 10 deletions(-) diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index 7cf062cc7..cb767d6ac 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX10_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_dx10.h b/backends/imgui_impl_dx10.h index 427d89b95..6f75693f0 100644 --- a/backends/imgui_impl_dx10.h +++ b/backends/imgui_impl_dx10.h @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX10_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index a15f1ab25..ff0a607c4 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX11_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_dx11.h b/backends/imgui_impl_dx11.h index 482b360a2..2fe392dfa 100644 --- a/backends/imgui_impl_dx11.h +++ b/backends/imgui_impl_dx11.h @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX11_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 811600b66..e5b314eb1 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX12_GetRenderState(). // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index fb9085c58..a8f3a6cf6 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -5,7 +5,7 @@ // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplDX12_GetRenderState(). // The aim of imgui_impl_dx12.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 6d3e2751f..a3b0502ae 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -7,6 +7,7 @@ // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplOpenGL3_GetRenderState(). // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h index a93804213..175598600 100644 --- a/backends/imgui_impl_opengl3.h +++ b/backends/imgui_impl_opengl3.h @@ -7,6 +7,7 @@ // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplOpenGL3_GetRenderState(). // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 2a73f1b05..53b47d434 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'SDL_GPUTexture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! **IMPORTANT** Before 2025/08/08, ImTextureID was a reference to a SDL_GPUTextureSamplerBinding struct. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLGPU3_GetRenderState(). // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ diff --git a/backends/imgui_impl_sdlgpu3.h b/backends/imgui_impl_sdlgpu3.h index 9c7d56e46..ce216fef0 100644 --- a/backends/imgui_impl_sdlgpu3.h +++ b/backends/imgui_impl_sdlgpu3.h @@ -5,6 +5,7 @@ // [X] Renderer: User texture binding. Use 'SDL_GPUTexture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! **IMPORTANT** Before 2025/08/08, ImTextureID was a reference to a SDL_GPUTextureSamplerBinding struct. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLGPU3_GetRenderState(). // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 3db26573c..4e970f97e 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -13,7 +13,7 @@ // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLRenderer3_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index 5559a5035..13efcba27 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -13,7 +13,7 @@ // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplSDLRenderer3_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 795c87a1a..9eca9a767 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -5,7 +5,7 @@ // [!] Renderer: User texture binding. Use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplVulkan_GetRenderState(). // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 646d228c9..a48dae3ec 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -5,7 +5,7 @@ // [!] Renderer: User texture binding. Use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplVulkan_GetRenderState(). // The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index b57035a06..bdc4d434b 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -5,8 +5,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplWGPU_GetRenderState(). // Read imgui_impl_wgpu.h about how to use the IMGUI_IMPL_WEBGPU_BACKEND_WGPU or IMGUI_IMPL_WEBGPU_BACKEND_DAWN flags. diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index 707c12772..a5867c923 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -17,8 +17,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). -// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: Expose selected render state for draw callbacks to use. Access with ImGui_ImplWGPU_GetRenderState(). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. From b5135f270f3d43209a9ff4c0075dc46347be52eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 15:25:54 +0200 Subject: [PATCH 223/254] Backends: SDLRenderer3: added support for platform_io.DrawCallback_SetSamplerFromTex. (#9378) --- backends/imgui_impl_sdlrenderer3.cpp | 46 ++++++++++++++++++++-------- backends/imgui_impl_sdlrenderer3.h | 2 +- docs/CHANGELOG.txt | 8 +++++ imgui.cpp | 2 +- imgui.h | 1 + 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 4e970f97e..121ce59c8 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -25,7 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-09-17: Restore modified texture scale modes at the end of RenderDrawData(). Added CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState (#9543, #9378) +// 2026-09-17: Restore modified texture scale modes at the end of RenderDrawData(). Added DrawCallback_SetSamplerFromTex support. Added CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState (#9543, #9378) // 2026-07-15: Fixed default sampler state to be linear (broken 2026-04-23). (#9470, #9378) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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) @@ -122,6 +122,7 @@ static int SDL_RenderGeometryRaw8BitColor(SDL_Renderer* renderer, ImVectorCurrentScaleMode = SDL_SCALEMODE_LINEAR; } static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_NEAREST; } +static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerFromTex(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplSDLRenderer3_RenderState* render_state = ImGui_ImplSDLRenderer3_GetRenderState(); render_state->CurrentScaleMode = SDL_SCALEMODE_INVALID; } // SDL_SCALEMODE_INVALID = use/keep value in texture. // SDLRenderer has various design issues which makes it hard to do it compared to regular graphics API. // - scale/filtering mode is tied to a texture. @@ -129,18 +130,38 @@ static void ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest(const ImDrawLi static void ImGui_ImplSDLRenderer3_TexScaleModeUpdate(SDL_Renderer* renderer, SDL_Texture* tex, SDL_ScaleMode desired_scale_mode) { ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); - SDL_ScaleMode tex_scale_mode = desired_scale_mode; - if (!SDL_GetTextureScaleMode(tex, &tex_scale_mode) || tex_scale_mode == desired_scale_mode) - return; - SDL_FlushRenderer(renderer); - SDL_SetTextureScaleMode(tex, desired_scale_mode); - - // Store backup of old scale mode - for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) - if (entry.Texture == tex) + if (desired_scale_mode != SDL_SCALEMODE_INVALID) + { + // Enforce a scale mode + SDL_ScaleMode tex_scale_mode = desired_scale_mode; + if (!SDL_GetTextureScaleMode(tex, &tex_scale_mode) || tex_scale_mode == desired_scale_mode) return; - ImGui_ImplSDLRenderer3_TexScaleModeEntry entry = { tex, tex_scale_mode }; - bd->TexScaleModeBackups.push_back(entry); + SDL_FlushRenderer(renderer); + SDL_SetTextureScaleMode(tex, desired_scale_mode); + + // Store backup of old scale mode (FIXME-OPT) + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.Texture == tex) + { + if (entry.ScaleModeBackup == SDL_SCALEMODE_INVALID) + entry.ScaleModeBackup = tex_scale_mode; + return; + } + ImGui_ImplSDLRenderer3_TexScaleModeEntry entry = { tex, tex_scale_mode }; + bd->TexScaleModeBackups.push_back(entry); + } + else + { + // Use scale mode that was set in texture by user. If there is an entry, restore it and clear the entry. + for (ImGui_ImplSDLRenderer3_TexScaleModeEntry& entry : bd->TexScaleModeBackups) + if (entry.Texture == tex && entry.ScaleModeBackup != SDL_SCALEMODE_INVALID) + { + SDL_FlushRenderer(renderer); + SDL_SetTextureScaleMode(tex, entry.ScaleModeBackup); + entry.ScaleModeBackup = SDL_SCALEMODE_INVALID; + return; + } + } } static void ImGui_ImplSDLRenderer3_TexScaleModeRestoreBackups(SDL_Renderer* renderer) @@ -345,6 +366,7 @@ bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer) platform_io.DrawCallback_ResetRenderState = ImGui_ImplSDLRenderer3_DrawCallback_ResetRenderState; platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerLinear; platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_SetSamplerFromTex = ImGui_ImplSDLRenderer3_DrawCallback_SetSamplerFromTex; bd->Renderer = renderer; diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index 13efcba27..019def26a 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -49,7 +49,7 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex) struct ImGui_ImplSDLRenderer3_RenderState { SDL_Renderer* Renderer; - SDL_ScaleMode CurrentScaleMode; // Current scale mode during render. + SDL_ScaleMode CurrentScaleMode; // Current scale mode during render. Set to SDL_SCALEMODE_INVALID to use "SetSamplerFromTex" mode. }; static inline ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() { return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6ba8bcfdd..4ff37a224 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -100,6 +100,9 @@ Other Changes: - Reworked assert in PushTexture() to allow convenience grace period of using a texture that was queue for destroy during the frame. Make it safe to stick to an existing texture during the frame. (#9528, #8465) + - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic + draw callback for requesting to use the sampler associated already associated to a texture. + Currently only supported by the SDLRenderer3 backend. (#9378) - Demo: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Backends: @@ -108,6 +111,11 @@ Other Changes: going through a SDL_Renderer. (#5592) [@lailoken] - SDLRenderer3: restore any temporarily modified texture scale modes at the of `RenderDrawData()`. (#9378) - SDLRenderer3: expose CurrentScaleMode in ImGui_ImplSDLRenderer3_RenderState. (#9543, #9378) [@Clownacy] + - SDLRenderer3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw callback to request + using the sampler associated already associated to a texture via `SDL_SetTextureScaleMode()`. (#9378) + It is equivalent to setting `render_state->CurrentScaleMode = SDL_SCALEMODE_INVALID`. + Note: this callback is only supported by selected rendering backends: most other graphics API + don't have the equivalent of `SDL_SetTextureScaleMode()` and the callback won't work with them. - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] diff --git a/imgui.cpp b/imgui.cpp index 90a80a3f8..9e5880ebd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -16111,7 +16111,7 @@ void ImGuiPlatformIO::ClearRendererHandlers() { Renderer_TextureMaxWidth = Renderer_TextureMaxHeight = 0; Renderer_RenderState = NULL; - DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = NULL; + DrawCallback_ResetRenderState = DrawCallback_SetSamplerLinear = DrawCallback_SetSamplerNearest = DrawCallback_SetSamplerFromTex = NULL; } ImGuiViewport* ImGui::GetMainViewport() diff --git a/imgui.h b/imgui.h index fe4f82d58..53f1e8660 100644 --- a/imgui.h +++ b/imgui.h @@ -4114,6 +4114,7 @@ struct ImGuiPlatformIO ImDrawCallback DrawCallback_ResetRenderState; // Request to reset the graphics/render state. ImDrawCallback DrawCallback_SetSamplerLinear; // Request backend to set texture sampling to Linear. ImDrawCallback DrawCallback_SetSamplerNearest; // Request backend to set texture sampling to Nearest/Point. + ImDrawCallback DrawCallback_SetSamplerFromTex; // Request backend to use sampler associated to texture - only available in some backends: SDLRenderer3. //ImDrawCallback DrawCallback_SetSamplerCustom; // Request backend to set texture sampling using Backend Specific data. //------------------------------------------------------------------ From 333f5186e3f9137bfac6487896ff041c870389e8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 19:17:41 +0200 Subject: [PATCH 224/254] Backends: OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not reseting all states in the glTexParameter() path. (#9378) + made OpenGL3 SetSamplerLinear/SetSamplerNearest check UseBindSampler for consistency. --- backends/imgui_impl_opengl2.cpp | 2 ++ backends/imgui_impl_opengl3.cpp | 17 ++++++++--------- backends/imgui_impl_opengl3.h | 4 ++-- docs/CHANGELOG.txt | 2 ++ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 8fe9d489f..713a7b0ba 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -132,6 +132,8 @@ static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_wid glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glShadeModel(GL_SMOOTH); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + bd->UseTexParameterToSetSampler = false; bd->NextSampler = GL_LINEAR; // If you are using this code with non-legacy OpenGL header/contexts (which you should not, prefer using imgui_impl_opengl3.cpp!!), diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index a3b0502ae..e45b2c299 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -387,9 +387,12 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, ImGui_Impl glUniform1i(bd->AttribLocationTex, 0); glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); - (void)render_state; + render_state->UseBindSampler = bd->HasBindSampler; + render_state->UseTexParameterFilter = false; + render_state->CurrentSampler = 0; + render_state->CurrentTexParameterFilter = 0; #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - if (render_state->UseBindSampler) + if (bd->HasBindSampler && render_state->UseBindSampler) { render_state->CurrentSampler = bd->TexSamplers[0]; glBindSampler(0, render_state->CurrentSampler); // We use combined texture/sampler state. Applications using GL 3.3 and GL ES 3.0 may set that otherwise. @@ -419,7 +422,7 @@ static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, c ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->HasBindSampler) + if (bd->HasBindSampler && render_state->UseBindSampler) { render_state->CurrentSampler = bd->TexSamplers[0]; render_state->UseTexParameterFilter = false; @@ -437,7 +440,7 @@ static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->HasBindSampler) + if (bd->HasBindSampler && render_state->UseBindSampler) { render_state->CurrentSampler = bd->TexSamplers[1]; render_state->UseTexParameterFilter = false; @@ -522,11 +525,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Setup render state structure (for callbacks and custom texture bindings) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - ImGui_ImplOpenGL3_RenderState render_state; - render_state.UseBindSampler = bd->HasBindSampler; - render_state.UseTexParameterFilter = false; - render_state.CurrentSampler = 0; - render_state.CurrentTexParameterFilter = 0; + ImGui_ImplOpenGL3_RenderState render_state = {}; platform_io.Renderer_RenderState = &render_state; ImGui_ImplOpenGL3_SetupRenderState(draw_data, &render_state, fb_width, fb_height, vertex_array_object); diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h index 175598600..b5bcc7400 100644 --- a/backends/imgui_impl_opengl3.h +++ b/backends/imgui_impl_opengl3.h @@ -73,8 +73,8 @@ struct ImGui_ImplOpenGL3_RenderState { bool UseBindSampler; bool UseTexParameterFilter; - unsigned int CurrentSampler; // (GLuint) Used if UseBindSampler == true, otherwise always 0 - unsigned int CurrentTexParameterFilter; // (GLuint) Used if UseTexParameterToSetSampler == true + unsigned int CurrentSampler; // (GLuint) Used if UseBindSampler == true (and are available), otherwise always 0 + unsigned int CurrentTexParameterFilter; // (GLuint) Used if UseTexParameterFilter == true }; static inline ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState() { return (ImGui_ImplOpenGL3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 4ff37a224..a9981e694 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -107,6 +107,8 @@ Other Changes: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not reseting all states + in the glTexParameter() path. (#9378) - SDL2: fixed querying framebuffer scale/density when using Metal without going through a SDL_Renderer. (#5592) [@lailoken] - SDLRenderer3: restore any temporarily modified texture scale modes at the of `RenderDrawData()`. (#9378) From 25e16fd8b68eca0ec46a420ea98a6d9ae93b6dbb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 20:22:33 +0200 Subject: [PATCH 225/254] Backends: OpenGL3: minor refactor for both sampler callback to share more code + comments. (#9378) --- backends/imgui_impl_opengl2.cpp | 11 +++--- backends/imgui_impl_opengl3.cpp | 64 ++++++++++++++------------------- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 713a7b0ba..503caf992 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -26,7 +26,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-09-07: OpenGL: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 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) @@ -160,9 +160,12 @@ static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_wid } // Draw callbacks -static void ImGui_ImplOpenGL2_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. -static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } -static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } +// - IMPORTANT: Using DrawCallback_SetSamplerLinear/DrawCallback_SetSamplerNearest will trash glTexParameteri() values for the texture!! +// We cannot decently backup/restore that state because glGetTexParameteriv() is slow on many setups. +// - Using DrawCallback_SetSamplerFromTex will basically instruct the backend to use existing glTexParameteri() values without modifying anything, which is the initial state. +static void ImGui_ImplOpenGL2_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } +static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } // OpenGL2 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index e45b2c299..ed5e7eab3 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -24,7 +24,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-09-07: OpenGL: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. // 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) @@ -415,44 +415,32 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, ImGui_Impl GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col))); } +static void ImGui_ImplOpenGL3_DrawCallback_SetSampler(unsigned int sampler_idx, unsigned int tex_parameter_filter) +{ + ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); + IM_UNUSED(sampler_idx); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->HasBindSampler && render_state->UseBindSampler) + { + render_state->CurrentSampler = bd->TexSamplers[sampler_idx]; + render_state->UseTexParameterFilter = false; + glBindSampler(0, render_state->CurrentSampler); + } + else +#endif + { + render_state->UseTexParameterFilter = true; + render_state->CurrentTexParameterFilter = tex_parameter_filter; + } +} + // Draw callbacks -static void ImGui_ImplOpenGL3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. -static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) -{ - ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); -#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->HasBindSampler && render_state->UseBindSampler) - { - render_state->CurrentSampler = bd->TexSamplers[0]; - render_state->UseTexParameterFilter = false; - glBindSampler(0, render_state->CurrentSampler); - } - else -#endif - { - render_state->UseTexParameterFilter = true; - render_state->CurrentTexParameterFilter = GL_LINEAR; - } -} -static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) -{ - ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); -#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER - ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - if (bd->HasBindSampler && render_state->UseBindSampler) - { - render_state->CurrentSampler = bd->TexSamplers[1]; - render_state->UseTexParameterFilter = false; - glBindSampler(0, render_state->CurrentSampler); - } - else -#endif - { - render_state->UseTexParameterFilter = true; - render_state->CurrentTexParameterFilter = GL_NEAREST; - } -} +// - IMPORTANT: Using DrawCallback_SetSamplerLinear/DrawCallback_SetSamplerNearest will trash glTexParameteri() values for the texture if used on a GL context not supporting glBindSampler()!! +// We cannot decently backup/restore that state because glGetTexParameteriv() is slow on many setups. +static void ImGui_ImplOpenGL3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_DrawCallback_SetSampler(0, GL_LINEAR); } +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_DrawCallback_SetSampler(1, GL_NEAREST); } // OpenGL3 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. From eabff0fd081ec1964c8ccab076327bc7c3678dcd Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 20:34:07 +0200 Subject: [PATCH 226/254] Backends: OpenGL2/OpenGL3: Added support for platform_io.DrawCallback_SetSamplerFromTex. (#9378) --- backends/imgui_impl_opengl2.cpp | 3 +++ backends/imgui_impl_opengl3.cpp | 17 +++++++++++++++++ docs/CHANGELOG.txt | 2 ++ imgui.h | 2 +- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 503caf992..88eed8c5f 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -26,6 +26,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-17: OpenGL: Added support for platform_io.DrawCallback_SetSamplerFromTex. (#9378) // 2026-09-07: OpenGL: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -166,6 +167,7 @@ static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_wid static void ImGui_ImplOpenGL2_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_LINEAR; } static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = true; bd->NextSampler = GL_NEAREST; } +static void ImGui_ImplOpenGL2_DrawCallback_SetSamplerFromTex(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); bd->UseTexParameterToSetSampler = false; } // OpenGL2 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. @@ -365,6 +367,7 @@ bool ImGui_ImplOpenGL2_Init() platform_io.DrawCallback_ResetRenderState = ImGui_ImplOpenGL2_DrawCallback_ResetRenderState; platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplOpenGL2_DrawCallback_SetSamplerLinear; platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplOpenGL2_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_SetSamplerFromTex = ImGui_ImplOpenGL2_DrawCallback_SetSamplerFromTex; return true; } diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index ed5e7eab3..1d340f637 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -24,6 +24,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-17: OpenGL: Added support for platform_io.DrawCallback_SetSamplerFromTex. (#9378) // 2026-09-07: OpenGL: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. @@ -438,9 +439,24 @@ static void ImGui_ImplOpenGL3_DrawCallback_SetSampler(unsigned int sampler_idx, // Draw callbacks // - IMPORTANT: Using DrawCallback_SetSamplerLinear/DrawCallback_SetSamplerNearest will trash glTexParameteri() values for the texture if used on a GL context not supporting glBindSampler()!! // We cannot decently backup/restore that state because glGetTexParameteriv() is slow on many setups. +// - Using DrawCallback_SetSamplerFromTex will basically instruct the backend to use existing glTexParameteri() values without modifying anything. static void ImGui_ImplOpenGL3_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_DrawCallback_SetSampler(0, GL_LINEAR); } static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplOpenGL3_DrawCallback_SetSampler(1, GL_NEAREST); } +static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerFromTex(const ImDrawList*, const ImDrawCmd*) +{ + ImGui_ImplOpenGL3_RenderState* render_state = ImGui_ImplOpenGL3_GetRenderState(); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->HasBindSampler) + { + render_state->CurrentSampler = 0; + glBindSampler(0, 0); + } +#endif + render_state->UseBindSampler = false; + render_state->UseTexParameterFilter = false; +} // OpenGL3 Render function. // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly. @@ -1083,6 +1099,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) platform_io.DrawCallback_ResetRenderState = ImGui_ImplOpenGL3_DrawCallback_ResetRenderState; platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplOpenGL3_DrawCallback_SetSamplerLinear; platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest; + platform_io.DrawCallback_SetSamplerFromTex = ImGui_ImplOpenGL3_DrawCallback_SetSamplerFromTex; // Store GLSL version string so we can refer to it later in case we recreate shaders. // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a9981e694..7a0fa2597 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -107,6 +107,8 @@ Other Changes: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - OpenGL2, OpenGL3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw + callback to request using the sampler associated already associated to a texture. (#9378) - OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not reseting all states in the glTexParameter() path. (#9378) - SDL2: fixed querying framebuffer scale/density when using Metal without diff --git a/imgui.h b/imgui.h index 53f1e8660..fe8531083 100644 --- a/imgui.h +++ b/imgui.h @@ -4114,7 +4114,7 @@ struct ImGuiPlatformIO ImDrawCallback DrawCallback_ResetRenderState; // Request to reset the graphics/render state. ImDrawCallback DrawCallback_SetSamplerLinear; // Request backend to set texture sampling to Linear. ImDrawCallback DrawCallback_SetSamplerNearest; // Request backend to set texture sampling to Nearest/Point. - ImDrawCallback DrawCallback_SetSamplerFromTex; // Request backend to use sampler associated to texture - only available in some backends: SDLRenderer3. + ImDrawCallback DrawCallback_SetSamplerFromTex; // Request backend to use sampler associated to texture - only available in some backends: OpenGL2/3 and SDLRenderer3. //ImDrawCallback DrawCallback_SetSamplerCustom; // Request backend to set texture sampling using Backend Specific data. //------------------------------------------------------------------ From 5a0ac1678c298dbff0d4aad7e82fb82ba989b20b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2026 21:09:14 +0200 Subject: [PATCH 227/254] Minor rename. --- docs/CHANGELOG.txt | 2 +- imgui_widgets.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 7a0fa2597..c83710ec0 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -102,7 +102,7 @@ Other Changes: an existing texture during the frame. (#9528, #8465) - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic draw callback for requesting to use the sampler associated already associated to a texture. - Currently only supported by the SDLRenderer3 backend. (#9378) + Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) - Demo: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Backends: diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index bb5a79005..0c6c31f5f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3177,18 +3177,18 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ float clicked_t = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse) { - const float mouse_abs_pos = g.IO.MousePos[axis]; + const float mouse_pos = g.IO.MousePos[axis]; if (g.ActiveIdIsJustActivated) { float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) grab_t = 1.0f - grab_t; const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); - const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. - g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; + const bool clicked_around_grab = (mouse_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. + g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_pos - grab_pos : 0.0f; } if (slider_usable_sz > 0.0f) - clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + clicked_t = ImSaturate((mouse_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); if (axis == ImGuiAxis_Y) clicked_t = 1.0f - clicked_t; set_new_value = true; From 6d07f6eb09b8046269a94902d508f24c594db09c Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 15:49:00 +0200 Subject: [PATCH 228/254] Selectable: fixed `ImGuiSelectableFlags_SelectOnNav` triggering during NavInit. Style Editor: fixed style/colors combo box applying style when opening Combo. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 4 +++- imgui_internal.h | 1 + imgui_widgets.cpp | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c83710ec0..6ec0298bd 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -66,6 +66,8 @@ Other Changes: - Using <> scrolling buttons while the selected Tab is at either end of the section mid-section but not fully visible always makes it fully visible (before eventually selecting a Tab in the leading/trailing section) +- Selectable: + - Fixed `ImGuiSelectableFlags_SelectOnNav` triggering during NavInit. - Fonts: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of @@ -105,6 +107,7 @@ Other Changes: Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) - Demo: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) + - Style Editor: fixed style/colors combo box applying style when opening Combo. - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - OpenGL2, OpenGL3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw diff --git a/imgui.cpp b/imgui.cpp index 9e5880ebd..c6decca34 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4337,7 +4337,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0; NavJustMovedToKeyMods = ImGuiMod_None; - NavJustMovedToIsTabbing = false; + NavJustMovedToIsTabbing = NavJustMovedToIsInit = false; NavJustMovedToHasSelectionData = false; // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... @@ -14174,6 +14174,7 @@ void ImGui::NavInitRequestApplyResult() g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = 0; g.NavJustMovedToIsTabbing = false; + g.NavJustMovedToIsInit = true; g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; } @@ -14441,6 +14442,7 @@ void ImGui::NavMoveRequestApplyResult() g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = g.NavMoveKeyMods; g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; + g.NavJustMovedToIsInit = false; g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; //IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId); } diff --git a/imgui_internal.h b/imgui_internal.h index a248fc800..4a3fdca18 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2445,6 +2445,7 @@ struct ImGuiContext ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). ImGuiKeyChord NavJustMovedToKeyMods; bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. + bool NavJustMovedToIsInit; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. // Navigation: extra config options (will be made public eventually) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 0c6c31f5f..1f60853b6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7616,7 +7616,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) // - (2) usage will fail with clipped items // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId && !g.NavJustMovedToIsInit) if (g.NavJustMovedToId == id && (g.NavJustMovedToKeyMods & ImGuiMod_Ctrl) == 0) selected = pressed = auto_selected = true; } From f09485d7bed3cc471d2a1310e4fa5b1693af03b7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 16:05:56 +0200 Subject: [PATCH 229/254] Style: inverted ImGuiCol_TitleBgXXX colors for StyleColorsLight(). So that the selected window title bar appears brighter. --- docs/CHANGELOG.txt | 3 +++ imgui_draw.cpp | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6ec0298bd..c5c131930 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -81,6 +81,9 @@ Other Changes: - Amended context menu right-click opening code to check that the right-click hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) +- Style: + - Inverted `ImGuiCol_TitleBgXXX` colors for `StyleColorsLight()` so that the selected + window title bar appears brighter. - Misc: - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values. Supported by: Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), ColorXXX() diff --git a/imgui_draw.cpp b/imgui_draw.cpp index bf7aebad1..0e76fcefc 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -336,9 +336,9 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_TitleBg] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.43f); colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); From c8fc1829d0253edaa5b0e5c92a1e8f17b7d15f14 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 16:51:18 +0200 Subject: [PATCH 230/254] ImGuiTextFilter: minor tweaks, made ImGuiTextRange fields match upcoming ImStrv. Removed exposed ImGuiTextRange helpers. Rename CountGrep to CountInclude (#3906) Toward #2435 --- imgui.cpp | 36 ++++++++++++++++++------------------ imgui.h | 26 +++++++++++++------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c6decca34..43594c4ab 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3093,7 +3093,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; - CountGrep = 0; + CountInclude = 0; if (default_filter) { ImStrncpy(InputBuf, default_filter, IM_COUNTOF(InputBuf)); @@ -3111,7 +3111,7 @@ bool ImGuiTextFilter::Draw(const char* label, float width) return value_changed; } -void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector* out) const +static void ImStrSplit(const char* b, const char* e, char separator, ImVector* out) { out->resize(0); const char* wb = b; @@ -3120,32 +3120,32 @@ void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVectorpush_back(ImGuiTextRange(wb, we)); + out->push_back(ImGuiTextFilter::ImGuiTextRange(wb, we)); wb = we + 1; } we++; } if (wb != we) - out->push_back(ImGuiTextRange(wb, we)); + out->push_back(ImGuiTextFilter::ImGuiTextRange(wb, we)); } void ImGuiTextFilter::Build() { Filters.resize(0); ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf)); - input_range.split(',', &Filters); + ImStrSplit(InputBuf, InputBuf + ImStrlen(InputBuf), ',', &Filters); - CountGrep = 0; + CountInclude = 0; for (ImGuiTextRange& f : Filters) { - while (f.b < f.e && ImCharIsBlankA(f.b[0])) - f.b++; - while (f.e > f.b && ImCharIsBlankA(f.e[-1])) - f.e--; - if (f.empty()) + while (f.Begin < f.End && ImCharIsBlankA(f.Begin[0])) + f.Begin++; + while (f.End > f.Begin && ImCharIsBlankA(f.End[-1])) + f.End--; + if (f.Begin == f.End) continue; - if (f.b[0] != '-') - CountGrep += 1; + if (f.Begin[0] != '-') + CountInclude += 1; } } @@ -3159,24 +3159,24 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const for (const ImGuiTextRange& f : Filters) { - if (f.b == f.e) + if (f.Begin == f.End) continue; - if (f.b[0] == '-') + if (f.Begin[0] == '-') { // Subtract - if (ImStristr(text, text_end, f.b + 1, f.e) != NULL) + if (ImStristr(text, text_end, f.Begin + 1, f.End) != NULL) return false; } else { // Grep - if (ImStristr(text, text_end, f.b, f.e) != NULL) + if (ImStristr(text, text_end, f.Begin, f.End) != NULL) return true; } } // Implicit * grep - if (CountGrep == 0) + if (CountInclude == 0) return true; return false; diff --git a/imgui.h b/imgui.h index fe8531083..a58e944ef 100644 --- a/imgui.h +++ b/imgui.h @@ -2789,26 +2789,26 @@ struct ImGuiOnceUponAFrame struct ImGuiTextFilter { IMGUI_API ImGuiTextFilter(const char* default_filter = ""); - IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; - IMGUI_API void Build(); - void Clear() { InputBuf[0] = 0; Build(); } - bool IsActive() const { return !Filters.empty(); } + IMGUI_API void Build(); // Update internal data when filter changes + inline void Clear() { InputBuf[0] = 0; Build(); } // Clear filter + inline bool IsActive() const { return Filters.Size != 0; } // Useful if you need e.g. an alternative code-path when there are no filters - // [Internal] + // Helper to call InputText() + Build() when buffer is changed. + IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); + + // [Internal] Don't use! Will be replaced with ImStrv. struct ImGuiTextRange { - const char* b; - const char* e; - - ImGuiTextRange() { b = e = NULL; } - ImGuiTextRange(const char* _b, const char* _e) { b = _b; e = _e; } - bool empty() const { return b == e; } - IMGUI_API void split(char separator, ImVector* out) const; + const char* Begin; + const char* End; + ImGuiTextRange(const char* b, const char* e) { Begin = b; End = e; } }; + + // [Internal] Members char InputBuf[256]; ImVectorFilters; - int CountGrep; + int CountInclude; }; // Helper: Growable text buffer for logging/accumulating text From f7ada78e32a1e0758ca209aba7f4ceb2fa9e5b7e Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 17:38:24 +0200 Subject: [PATCH 231/254] ImGuiTextFilter: added DrawWithHint(). (#6206, #6395, #6447) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 8 +++++++- imgui.h | 3 ++- imgui_demo.cpp | 21 ++++++++++++++------- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c5c131930..3eecd0dba 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -81,6 +81,9 @@ Other Changes: - Amended context menu right-click opening code to check that the right-click hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) +- ImGuiTextFilter: + - Added `DrawWithHint()` function. (#6206, #6395, #6447) + - Added a "incl,-excl" hint by default when the search field is empty. (#6206, #6395, #6447) - Style: - Inverted `ImGuiCol_TitleBgXXX` colors for `StyleColorsLight()` so that the selected window title bar appears brighter. diff --git a/imgui.cpp b/imgui.cpp index 43594c4ab..aabf2e942 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3105,7 +3105,13 @@ bool ImGuiTextFilter::Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); - bool value_changed = ImGui::InputText(label, InputBuf, IM_COUNTOF(InputBuf)); + return DrawWithHint(label); +} + +// Use ImGui::SetNextItemWidth() manually if you want to use this. +bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) +{ + bool value_changed = ImGui::InputTextWithHint(label, hint, InputBuf, IM_COUNTOF(InputBuf)); if (value_changed) Build(); return value_changed; diff --git a/imgui.h b/imgui.h index a58e944ef..12b0dfb1b 100644 --- a/imgui.h +++ b/imgui.h @@ -2795,7 +2795,8 @@ struct ImGuiTextFilter inline bool IsActive() const { return Filters.Size != 0; } // Useful if you need e.g. an alternative code-path when there are no filters // Helper to call InputText() + Build() when buffer is changed. - IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); + IMGUI_API bool Draw(const char* label = "Filter", float width = 0.0f); + IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl,-excl"); // [Internal] Don't use! Will be replaced with ImStrv. struct ImGuiTextRange diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7478a99ee..c5e0697aa 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1452,7 +1452,8 @@ static void DemoWindowWidgetsComboBoxes() filter.Clear(); } ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F); - filter.Draw("##Filter", -FLT_MIN); + ImGui::SetNextItemWidth(-FLT_MIN); + filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); for (int n = 0; n < IM_COUNTOF(items); n++) { @@ -3945,7 +3946,8 @@ static void DemoWindowWidgetsTextFilter() " \"xxx\" display lines containing \"xxx\"\n" " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" " \"-xxx\" hide lines containing \"xxx\""); - filter.Draw(); + ImGui::SetNextItemWidth(-FLT_MIN); + filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; for (int i = 0; i < IM_COUNTOF(lines); i++) if (filter.PassFilter(lines[i])) @@ -8820,9 +8822,6 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SameLine(); SetNextItemWidth(GetFontSize() * 10); Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); SameLine(); Checkbox("Only Modified Colors", &output_only_modified); - static ImGuiTextFilter filter; - filter.Draw("Filter colors", GetFontSize() * 16); - static ImGuiColorEditFlags alpha_flags = 0; if (RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_AlphaOpaque)) { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } SameLine(); if (RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } SameLine(); @@ -8832,6 +8831,10 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "Left-click on color square to open color picker,\n" "Right-click to open edit options menu."); + static ImGuiTextFilter filter; + SetNextItemWidth(-FLT_MIN); + filter.DrawWithHint("##FilterColors", "Filter Colors (incl,-excl)"); + SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); PushItemWidth(GetFontSize() * -12); @@ -9244,7 +9247,10 @@ struct ExampleAppConsole if (ImGui::Button("Options")) ImGui::OpenPopup("Options"); ImGui::SameLine(); - Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); + + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); + ImGui::SetNextItemWidth(-FLT_MIN); + Filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); ImGui::Separator(); // Reserve enough left-over height for 1 separator + 1 input text @@ -9579,7 +9585,8 @@ struct ExampleAppLog ImGui::SameLine(); bool copy = ImGui::Button("Copy"); ImGui::SameLine(); - Filter.Draw("Filter", -100.0f); + ImGui::SetNextItemWidth(-FLT_MIN); + Filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); ImGui::Separator(); From a14bebd948e021a7801a9d987f135332dc514519 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 17:50:29 +0200 Subject: [PATCH 232/254] (Breaking) ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`. (#6206, #6395, #6447) The reasoning is to (1) keep things consistent with DrawWithHint() without polluting the new function with width. (2) keep things consistent with other items. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 7 +++---- imgui.h | 5 ++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3eecd0dba..e0d1b70d8 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -45,6 +45,8 @@ Breaking Changes: favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. It is easier to tweak + this allows us to easily scale error threshold on high density screens. - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). +- ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: + prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. - Backends: - DirectX12: Removed support for legacy `ImGui_ImplDX12_Init()` signature, which was obsoleted in 1.91.6 (December 2014), because it needed to forcefully disable support diff --git a/imgui.cpp b/imgui.cpp index aabf2e942..d41f4d3fa 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/09/18 (1.93.0) - ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). - 2026/07/20 (1.92.9) - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: @@ -3101,11 +3102,9 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 } } -bool ImGuiTextFilter::Draw(const char* label, float width) +bool ImGuiTextFilter::Draw(const char* label) { - if (width != 0.0f) - ImGui::SetNextItemWidth(width); - return DrawWithHint(label); + return DrawWithHint(label, "incl,-excl"); } // Use ImGui::SetNextItemWidth() manually if you want to use this. diff --git a/imgui.h b/imgui.h index 12b0dfb1b..ce48dca17 100644 --- a/imgui.h +++ b/imgui.h @@ -2795,8 +2795,11 @@ struct ImGuiTextFilter inline bool IsActive() const { return Filters.Size != 0; } // Useful if you need e.g. an alternative code-path when there are no filters // Helper to call InputText() + Build() when buffer is changed. - IMGUI_API bool Draw(const char* label = "Filter", float width = 0.0f); + IMGUI_API bool Draw(const char* label = "Filter"); IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl,-excl"); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); } +#endif // [Internal] Don't use! Will be replaced with ImStrv. struct ImGuiTextRange From fc98dc4a72e196a4a3cb721861651ff882490cac Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 18:31:50 +0200 Subject: [PATCH 233/254] (Internal) ImGuiTextFilter: renaming ImGuiTextRange->ImGuiTextFilterItem, Filters->Items. (#2435) --- imgui.cpp | 26 +++++++++++++------------- imgui.h | 16 ++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d41f4d3fa..b2bdf55f0 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3094,7 +3094,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; - CountInclude = 0; + _CountInclude = 0; if (default_filter) { ImStrncpy(InputBuf, default_filter, IM_COUNTOF(InputBuf)); @@ -3116,7 +3116,7 @@ bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) return value_changed; } -static void ImStrSplit(const char* b, const char* e, char separator, ImVector* out) +static void ImStrSplit(const char* b, const char* e, char separator, ImVector* out) { out->resize(0); const char* wb = b; @@ -3125,23 +3125,23 @@ static void ImStrSplit(const char* b, const char* e, char separator, ImVectorpush_back(ImGuiTextFilter::ImGuiTextRange(wb, we)); + out->push_back(ImGuiTextFilter::ImGuiTextFilterItem(wb, we)); wb = we + 1; } we++; } if (wb != we) - out->push_back(ImGuiTextFilter::ImGuiTextRange(wb, we)); + out->push_back(ImGuiTextFilter::ImGuiTextFilterItem(wb, we)); } void ImGuiTextFilter::Build() { - Filters.resize(0); - ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf)); - ImStrSplit(InputBuf, InputBuf + ImStrlen(InputBuf), ',', &Filters); + _Items.resize(0); + ImGuiTextFilterItem input_range(InputBuf, InputBuf + ImStrlen(InputBuf)); + ImStrSplit(InputBuf, InputBuf + ImStrlen(InputBuf), ',', &_Items); - CountInclude = 0; - for (ImGuiTextRange& f : Filters) + _CountInclude = 0; + for (ImGuiTextFilterItem& f : _Items) { while (f.Begin < f.End && ImCharIsBlankA(f.Begin[0])) f.Begin++; @@ -3150,19 +3150,19 @@ void ImGuiTextFilter::Build() if (f.Begin == f.End) continue; if (f.Begin[0] != '-') - CountInclude += 1; + _CountInclude += 1; } } bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const { - if (Filters.Size == 0) + if (_Items.Size == 0) return true; if (text == NULL) text = text_end = ""; - for (const ImGuiTextRange& f : Filters) + for (const ImGuiTextFilterItem& f : _Items) { if (f.Begin == f.End) continue; @@ -3181,7 +3181,7 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const } // Implicit * grep - if (CountInclude == 0) + if (_CountInclude == 0) return true; return false; diff --git a/imgui.h b/imgui.h index ce48dca17..786afe386 100644 --- a/imgui.h +++ b/imgui.h @@ -2792,7 +2792,7 @@ struct ImGuiTextFilter IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; IMGUI_API void Build(); // Update internal data when filter changes inline void Clear() { InputBuf[0] = 0; Build(); } // Clear filter - inline bool IsActive() const { return Filters.Size != 0; } // Useful if you need e.g. an alternative code-path when there are no filters + inline bool IsActive() const { return _Items.Size != 0; } // Useful if you need e.g. an alternative code-path when there are no filters // Helper to call InputText() + Build() when buffer is changed. IMGUI_API bool Draw(const char* label = "Filter"); @@ -2802,17 +2802,17 @@ struct ImGuiTextFilter #endif // [Internal] Don't use! Will be replaced with ImStrv. - struct ImGuiTextRange + struct ImGuiTextFilterItem { - const char* Begin; - const char* End; - ImGuiTextRange(const char* b, const char* e) { Begin = b; End = e; } + const char* Begin; + const char* End; + ImGuiTextFilterItem(const char* b, const char* e) { Begin = b; End = e; } }; // [Internal] Members - char InputBuf[256]; - ImVectorFilters; - int CountInclude; + char InputBuf[256]; // User input buffer + int _CountInclude; // >= 0 + ImVector _Items; // Pre-parsed, trimmed items }; // Helper: Growable text buffer for logging/accumulating text From 54f86be71ea73b457cf66db600ca881725030131 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 22:02:57 +0200 Subject: [PATCH 234/254] ImGuiTextFilter: fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" match always filters out. (#2435) + fix typo in ImToUpper --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 71 +++++++++++++++++++++------------------------- imgui.h | 2 +- imgui_internal.h | 2 +- 4 files changed, 37 insertions(+), 40 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e0d1b70d8..672c24c30 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -84,6 +84,8 @@ Other Changes: hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - ImGuiTextFilter: + - Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" + match always filters out. (#2435) [@kudaba, @ocornut] - Added `DrawWithHint()` function. (#6206, #6395, #6447) - Added a "incl,-excl" hint by default when the search field is empty. (#6206, #6395, #6447) - Style: diff --git a/imgui.cpp b/imgui.cpp index b2bdf55f0..8968a8f3f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3116,74 +3116,69 @@ bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) return value_changed; } -static void ImStrSplit(const char* b, const char* e, char separator, ImVector* out) +static void ImGuiTextFilter_BuildAddItem(ImGuiTextFilter* f, const char* word_b, const char* word_e) { - out->resize(0); - const char* wb = b; - const char* we = wb; - while (we < e) - { - if (*we == separator) - { - out->push_back(ImGuiTextFilter::ImGuiTextFilterItem(wb, we)); - wb = we + 1; - } - we++; - } - if (wb != we) - out->push_back(ImGuiTextFilter::ImGuiTextFilterItem(wb, we)); + // Trim (FIXME: UTF-8 support?) + while (word_b < word_e && ImCharIsBlankA(word_b[0])) + word_b++; + while (word_e > word_b && ImCharIsBlankA(word_e[-1])) + word_e--; + if (word_e - word_b <= 0) + return; + + // Add to list + // FIXME-OPT: on push_front(): as N is derived from user inputs we expect this to be fine. + const bool is_excl = (word_b[0] == '-'); + f->_Items.insert(is_excl ? f->_Items.Data : f->_Items.end(), ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); + if (!is_excl) + f->_CountInclude++; } void ImGuiTextFilter::Build() { + // Parse filters _Items.resize(0); - ImGuiTextFilterItem input_range(InputBuf, InputBuf + ImStrlen(InputBuf)); - ImStrSplit(InputBuf, InputBuf + ImStrlen(InputBuf), ',', &_Items); - _CountInclude = 0; - for (ImGuiTextFilterItem& f : _Items) + const char* buf_e = InputBuf + ImStrlen(InputBuf); + const char* word_b = InputBuf; + const char* word_e = word_b; + while (word_e < buf_e) { - while (f.Begin < f.End && ImCharIsBlankA(f.Begin[0])) - f.Begin++; - while (f.End > f.Begin && ImCharIsBlankA(f.End[-1])) - f.End--; - if (f.Begin == f.End) - continue; - if (f.Begin[0] != '-') - _CountInclude += 1; + if (*word_e == ',') + { + ImGuiTextFilter_BuildAddItem(this, word_b, word_e); + word_b = word_e + 1; + } + word_e++; } + ImGuiTextFilter_BuildAddItem(this, word_b, word_e); } bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const { if (_Items.Size == 0) return true; - if (text == NULL) text = text_end = ""; - for (const ImGuiTextFilterItem& f : _Items) + // Filters are sorted so that '-' ones are always leading. + for (const ImGuiTextFilterItem& item : _Items) { - if (f.Begin == f.End) - continue; - if (f.Begin[0] == '-') + if (item.Begin[0] == '-') { - // Subtract - if (ImStristr(text, text_end, f.Begin + 1, f.End) != NULL) + if (ImStristr(text, text_end, item.Begin + 1, item.End) != NULL) // Exclude return false; } else { - // Grep - if (ImStristr(text, text_end, f.Begin, f.End) != NULL) + if (ImStristr(text, text_end, item.Begin, item.End) != NULL) // Include return true; } } - // Implicit * grep + // When no inclusion are specified (only exclusions) we implicitly pass if (_CountInclude == 0) return true; - return false; } diff --git a/imgui.h b/imgui.h index 786afe386..d66f70d24 100644 --- a/imgui.h +++ b/imgui.h @@ -2812,7 +2812,7 @@ struct ImGuiTextFilter // [Internal] Members char InputBuf[256]; // User input buffer int _CountInclude; // >= 0 - ImVector _Items; // Pre-parsed, trimmed items + ImVector _Items; // Pre-parsed, trimmed, reordered items }; // Helper: Growable text buffer for logging/accumulating text diff --git a/imgui_internal.h b/imgui_internal.h index 4a3fdca18..8b6ffb796 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -407,7 +407,7 @@ IMGUI_API const char* ImStrSkipBlank(const char* str); IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line IM_MSVC_RUNTIME_CHECKS_OFF -inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } +inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c & ~32 : c; } inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } From c74e7e93a3e0b92bf5da4ebf63f05284c1d32c9b Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 19:20:42 +0200 Subject: [PATCH 235/254] ImGuiTextFilter: Added support for space ' ' as separator. Added support for quoted "xxx" blocks. (#2435, #30) --- docs/CHANGELOG.txt | 4 ++- imgui.cpp | 66 ++++++++++++++++++++++++++-------------------- imgui.h | 4 +-- imgui_demo.cpp | 12 ++++----- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 672c24c30..82c2ffe00 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -84,10 +84,12 @@ Other Changes: hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - ImGuiTextFilter: + - Added support for space ' ' as separator, which is more standard. (#2435, #30) + - Added support for quoted "xxx" blocks to e.g. match words sequences. (#2435) - Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" match always filters out. (#2435) [@kudaba, @ocornut] - Added `DrawWithHint()` function. (#6206, #6395, #6447) - - Added a "incl,-excl" hint by default when the search field is empty. (#6206, #6395, #6447) + - Added a "incl -excl" hint by default when the search field is empty. (#6206, #6395, #6447) - Style: - Inverted `ImGuiCol_TitleBgXXX` colors for `StyleColorsLight()` so that the selected window title bar appears brighter. diff --git a/imgui.cpp b/imgui.cpp index 8968a8f3f..e0065362c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3090,7 +3090,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // [SECTION] ImGuiTextFilter //----------------------------------------------------------------------------- -// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +// Helper: Parse and apply text filters e.g. 'aaa bbb -ccc'. ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; @@ -3104,7 +3104,7 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 bool ImGuiTextFilter::Draw(const char* label) { - return DrawWithHint(label, "incl,-excl"); + return DrawWithHint(label, "incl -excl"); } // Use ImGui::SetNextItemWidth() manually if you want to use this. @@ -3118,17 +3118,20 @@ bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) static void ImGuiTextFilter_BuildAddItem(ImGuiTextFilter* f, const char* word_b, const char* word_e) { - // Trim (FIXME: UTF-8 support?) - while (word_b < word_e && ImCharIsBlankA(word_b[0])) - word_b++; - while (word_e > word_b && ImCharIsBlankA(word_e[-1])) - word_e--; - if (word_e - word_b <= 0) + // Trim blanks + if (word_b < word_e && word_b[0] != '\"') + { + while (word_b < word_e && ImCharIsBlankA(word_b[0])) // FIXME: UTF-8 support + word_b++; + while (word_e > word_b && ImCharIsBlankA(word_e[-1])) + word_e--; + } + const bool is_excl = (word_b < word_e && word_b[0] == '-'); + if (word_e - word_b - (is_excl ? 1 : 0) <= 0) return; // Add to list - // FIXME-OPT: on push_front(): as N is derived from user inputs we expect this to be fine. - const bool is_excl = (word_b[0] == '-'); + // FIXME-OPT: about ~push_front(): as N is derived from user inputs we expect this to be fine. f->_Items.insert(is_excl ? f->_Items.Data : f->_Items.end(), ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); if (!is_excl) f->_CountInclude++; @@ -3140,18 +3143,26 @@ void ImGuiTextFilter::Build() _Items.resize(0); _CountInclude = 0; const char* buf_e = InputBuf + ImStrlen(InputBuf); - const char* word_b = InputBuf; - const char* word_e = word_b; - while (word_e < buf_e) + const char* word_e; + for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1) { - if (*word_e == ',') + const bool is_excl = (word_b[0] == '-'); + if (word_b[0] == '\"' || (is_excl && word_b + 1 < buf_e && word_b[1] == '\"')) { - ImGuiTextFilter_BuildAddItem(this, word_b, word_e); - word_b = word_e + 1; + // Parsing "word". Store leading " to distinguish -"word" from "-word", but omit trailing ". + word_e = ImStrchrRange(word_b + (is_excl ? 2 : 1), buf_e, '\"'); + if (word_e == NULL) + word_e = buf_e; } - word_e++; + else + { + // Handle both ' ' and ',' separators. + for (word_e = word_b; word_e < buf_e; word_e++) + if (*word_e == ' ' || *word_e == ',') + break; + } + ImGuiTextFilter_BuildAddItem(this, word_b, word_e); } - ImGuiTextFilter_BuildAddItem(this, word_b, word_e); } bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const @@ -3164,16 +3175,15 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const // Filters are sorted so that '-' ones are always leading. for (const ImGuiTextFilterItem& item : _Items) { - if (item.Begin[0] == '-') - { - if (ImStristr(text, text_end, item.Begin + 1, item.End) != NULL) // Exclude - return false; - } - else - { - if (ImStristr(text, text_end, item.Begin, item.End) != NULL) // Include - return true; - } + const char* word_b = item.Begin; + const char* word_e = item.End; + const bool is_excl = (word_b[0] == '-'); + if (is_excl) + word_b++; + if (word_b < word_e && word_b[0] == '\"') + word_b++; + if (ImStristr(text, text_end, word_b, word_e) != NULL) + return is_excl ? false : true; } // When no inclusion are specified (only exclusions) we implicitly pass diff --git a/imgui.h b/imgui.h index d66f70d24..20fb5fc96 100644 --- a/imgui.h +++ b/imgui.h @@ -2785,7 +2785,7 @@ struct ImGuiOnceUponAFrame operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } }; -// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +// Helper: Parse and apply text filters e.g. 'aaa bbb -ccc'. struct ImGuiTextFilter { IMGUI_API ImGuiTextFilter(const char* default_filter = ""); @@ -2796,7 +2796,7 @@ struct ImGuiTextFilter // Helper to call InputText() + Build() when buffer is changed. IMGUI_API bool Draw(const char* label = "Filter"); - IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl,-excl"); + IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl -excl"); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); } #endif diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c5e0697aa..5ef47f3af 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1453,7 +1453,7 @@ static void DemoWindowWidgetsComboBoxes() } ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F); ImGui::SetNextItemWidth(-FLT_MIN); - filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); + filter.DrawWithHint("##Filter", "Filter (incl -excl)"); for (int n = 0; n < IM_COUNTOF(items); n++) { @@ -3947,7 +3947,7 @@ static void DemoWindowWidgetsTextFilter() " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" " \"-xxx\" hide lines containing \"xxx\""); ImGui::SetNextItemWidth(-FLT_MIN); - filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); + filter.DrawWithHint("##Filter", "Filter (incl -excl)"); const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; for (int i = 0; i < IM_COUNTOF(lines); i++) if (filter.PassFilter(lines[i])) @@ -8833,7 +8833,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) static ImGuiTextFilter filter; SetNextItemWidth(-FLT_MIN); - filter.DrawWithHint("##FilterColors", "Filter Colors (incl,-excl)"); + filter.DrawWithHint("##FilterColors", "Filter Colors (incl -excl)"); SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); @@ -9250,7 +9250,7 @@ struct ExampleAppConsole ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); ImGui::SetNextItemWidth(-FLT_MIN); - Filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); + Filter.DrawWithHint("##Filter", "Filter (incl -excl)"); ImGui::Separator(); // Reserve enough left-over height for 1 separator + 1 input text @@ -9586,7 +9586,7 @@ struct ExampleAppLog bool copy = ImGui::Button("Copy"); ImGui::SameLine(); ImGui::SetNextItemWidth(-FLT_MIN); - Filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); + Filter.DrawWithHint("##Filter", "Filter (incl -excl)"); ImGui::Separator(); @@ -9781,7 +9781,7 @@ struct ExampleAppPropertyEditor ImGui::Text("(%d root nodes)", root_node->Childs.Size); ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); - if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) + if (ImGui::InputTextWithHint("##Filter", "incl -excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) Filter.Build(); ImGui::PopItemFlag(); From c0041b7d3c1e29dda8f173f54dbbe15b285ff62b Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 19:29:02 +0200 Subject: [PATCH 236/254] ImGuiTextFilter: added undocumented MinWordSize support. (#2435) --- imgui.cpp | 3 ++- imgui.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index e0065362c..171097ef4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3094,6 +3094,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; + MinWordSize = 1; _CountInclude = 0; if (default_filter) { @@ -3127,7 +3128,7 @@ static void ImGuiTextFilter_BuildAddItem(ImGuiTextFilter* f, const char* word_b, word_e--; } const bool is_excl = (word_b < word_e && word_b[0] == '-'); - if (word_e - word_b - (is_excl ? 1 : 0) <= 0) + if (word_e - word_b - (is_excl ? 1 : 0) < f->MinWordSize) return; // Add to list diff --git a/imgui.h b/imgui.h index 20fb5fc96..c29e97d9d 100644 --- a/imgui.h +++ b/imgui.h @@ -2811,6 +2811,7 @@ struct ImGuiTextFilter // [Internal] Members char InputBuf[256]; // User input buffer + ImU8 MinWordSize; // == 1 int _CountInclude; // >= 0 ImVector _Items; // Pre-parsed, trimmed, reordered items }; From 565564bb5ce7ef0b194ed935e5d002d1ee499cd1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 20:41:16 +0200 Subject: [PATCH 237/254] ImGuiTextFilter: reworked items parsing/storage to be able to simplify PassFilter(). As PassFilter() is the bottleneck. --- imgui.cpp | 69 ++++++++++++++++++++++++------------------------------- imgui.h | 1 + 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 171097ef4..b017bcd5f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3095,7 +3095,7 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; MinWordSize = 1; - _CountInclude = 0; + _CountExclude = _CountInclude = 0; if (default_filter) { ImStrncpy(InputBuf, default_filter, IM_COUNTOF(InputBuf)); @@ -3117,41 +3117,26 @@ bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) return value_changed; } -static void ImGuiTextFilter_BuildAddItem(ImGuiTextFilter* f, const char* word_b, const char* word_e) -{ - // Trim blanks - if (word_b < word_e && word_b[0] != '\"') - { - while (word_b < word_e && ImCharIsBlankA(word_b[0])) // FIXME: UTF-8 support - word_b++; - while (word_e > word_b && ImCharIsBlankA(word_e[-1])) - word_e--; - } - const bool is_excl = (word_b < word_e && word_b[0] == '-'); - if (word_e - word_b - (is_excl ? 1 : 0) < f->MinWordSize) - return; - - // Add to list - // FIXME-OPT: about ~push_front(): as N is derived from user inputs we expect this to be fine. - f->_Items.insert(is_excl ? f->_Items.Data : f->_Items.end(), ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); - if (!is_excl) - f->_CountInclude++; -} - +// Parse filter and split into items void ImGuiTextFilter::Build() { - // Parse filters _Items.resize(0); - _CountInclude = 0; + _CountExclude = _CountInclude = 0; const char* buf_e = InputBuf + ImStrlen(InputBuf); const char* word_e; for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1) { - const bool is_excl = (word_b[0] == '-'); - if (word_b[0] == '\"' || (is_excl && word_b + 1 < buf_e && word_b[1] == '\"')) + // Trim blanks + while (word_b < buf_e && ImCharIsBlankA(word_b[0])) // FIXME: UTF-8 support + word_b++; + const bool is_excl = (word_b < buf_e && word_b[0] == '-'); + if (is_excl) + word_b++; + const bool is_quote = (word_b < buf_e && word_b[0] == '\"'); + if (is_quote) { - // Parsing "word". Store leading " to distinguish -"word" from "-word", but omit trailing ". - word_e = ImStrchrRange(word_b + (is_excl ? 2 : 1), buf_e, '\"'); + // Parsing quotes. Omit storing leading/trailing quotes. + word_e = ImStrchrRange(++word_b, buf_e, '\"'); if (word_e == NULL) word_e = buf_e; } @@ -3162,7 +3147,19 @@ void ImGuiTextFilter::Build() if (*word_e == ' ' || *word_e == ',') break; } - ImGuiTextFilter_BuildAddItem(this, word_b, word_e); + + // Min length + if (word_e - word_b < MinWordSize) + continue; + + // Add to list + // The '-' is not stored in items but implicitly inferred using (n < CountExclude). + // FIXME-OPT: about ~push_front(): as N is derived from user inputs we expect this to be fine. + _Items.insert(is_excl ? _Items.Data : _Items.Data + _Items.Size, ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); + if (is_excl) + _CountExclude++; + else + _CountInclude++; } } @@ -3174,17 +3171,11 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const text = text_end = ""; // Filters are sorted so that '-' ones are always leading. - for (const ImGuiTextFilterItem& item : _Items) + for (int n = 0; n < _Items.Size; n++) { - const char* word_b = item.Begin; - const char* word_e = item.End; - const bool is_excl = (word_b[0] == '-'); - if (is_excl) - word_b++; - if (word_b < word_e && word_b[0] == '\"') - word_b++; - if (ImStristr(text, text_end, word_b, word_e) != NULL) - return is_excl ? false : true; + const ImGuiTextFilterItem& item = _Items[n]; + if (ImStristr(text, text_end, item.Begin, item.End) != NULL) + return (n < _CountExclude) ? false : true; } // When no inclusion are specified (only exclusions) we implicitly pass diff --git a/imgui.h b/imgui.h index c29e97d9d..26afa5e7a 100644 --- a/imgui.h +++ b/imgui.h @@ -2812,6 +2812,7 @@ struct ImGuiTextFilter // [Internal] Members char InputBuf[256]; // User input buffer ImU8 MinWordSize; // == 1 + int _CountExclude; // >= 0 int _CountInclude; // >= 0 ImVector _Items; // Pre-parsed, trimmed, reordered items }; From 249e5a939dc6df0c31ac5d5a117ab27d3dc8ae2f Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 21:04:32 +0200 Subject: [PATCH 238/254] ImGuiTextFilter: added undocumented FilterOp support. (#2435) --- imgui.cpp | 19 ++++++++++++++----- imgui.h | 1 + 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b017bcd5f..f9dc89e53 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3094,6 +3094,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; + FilterOp = '|'; MinWordSize = 1; _CountExclude = _CountInclude = 0; if (default_filter) @@ -3122,6 +3123,7 @@ void ImGuiTextFilter::Build() { _Items.resize(0); _CountExclude = _CountInclude = 0; + IM_ASSERT(FilterOp == '|' || FilterOp == '&'); const char* buf_e = InputBuf + ImStrlen(InputBuf); const char* word_e; for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1) @@ -3171,17 +3173,24 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const text = text_end = ""; // Filters are sorted so that '-' ones are always leading. - for (int n = 0; n < _Items.Size; n++) + int n; + for (n = 0; n < _CountExclude; n++) + if (ImStristr(text, text_end, _Items.Data[n].Begin, _Items.Data[n].End) != NULL) + return false; + const bool is_and_filter = (FilterOp == '&'); + for (; n < _Items.Size; n++) { - const ImGuiTextFilterItem& item = _Items[n]; - if (ImStristr(text, text_end, item.Begin, item.End) != NULL) - return (n < _CountExclude) ? false : true; + const bool is_match = ImStristr(text, text_end, _Items.Data[n].Begin, _Items.Data[n].End) != NULL; + if (is_match && !is_and_filter) // or incl 1 -> true + return true; // or incl 0 -> continue + if (!is_match && is_and_filter) // and incl 1 -> continue + return false; // and incl 0 -> false } // When no inclusion are specified (only exclusions) we implicitly pass if (_CountInclude == 0) return true; - return false; + return is_and_filter; } //----------------------------------------------------------------------------- diff --git a/imgui.h b/imgui.h index 26afa5e7a..2ad3b91fb 100644 --- a/imgui.h +++ b/imgui.h @@ -2811,6 +2811,7 @@ struct ImGuiTextFilter // [Internal] Members char InputBuf[256]; // User input buffer + char FilterOp; // == '|' (any) pr '&' (all) ImU8 MinWordSize; // == 1 int _CountExclude; // >= 0 int _CountInclude; // >= 0 From 420f1793417e39560ca39a4209c55a9f204fde13 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2026 22:26:25 +0200 Subject: [PATCH 239/254] Backends: fixed Clang -Wold-style-cast warnings. Amend 8e17468 --- backends/imgui_impl_dx10.cpp | 5 +++++ backends/imgui_impl_dx10.h | 3 +-- backends/imgui_impl_dx11.cpp | 5 +++++ backends/imgui_impl_dx11.h | 3 +-- backends/imgui_impl_dx12.cpp | 5 +++++ backends/imgui_impl_dx12.h | 3 +-- backends/imgui_impl_opengl3.cpp | 5 +++++ backends/imgui_impl_opengl3.h | 3 +-- backends/imgui_impl_sdlgpu3.cpp | 5 +++++ backends/imgui_impl_sdlgpu3.h | 3 +-- backends/imgui_impl_sdlrenderer2.cpp | 5 +++++ backends/imgui_impl_sdlrenderer2.h | 3 +-- backends/imgui_impl_sdlrenderer3.cpp | 5 +++++ backends/imgui_impl_sdlrenderer3.h | 3 +-- backends/imgui_impl_vulkan.cpp | 5 +++++ backends/imgui_impl_vulkan.h | 3 +-- backends/imgui_impl_wgpu.cpp | 5 +++++ backends/imgui_impl_wgpu.h | 3 +-- 18 files changed, 54 insertions(+), 18 deletions(-) diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index cb767d6ac..a81d81305 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -101,6 +101,11 @@ static ImGui_ImplDX10_Data* ImGui_ImplDX10_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX10_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplDX10_RenderState* ImGui_ImplDX10_GetRenderState() +{ + return (ImGui_ImplDX10_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* device) { diff --git a/backends/imgui_impl_dx10.h b/backends/imgui_impl_dx10.h index 6f75693f0..3ab1669a9 100644 --- a/backends/imgui_impl_dx10.h +++ b/backends/imgui_impl_dx10.h @@ -46,7 +46,6 @@ struct ImGui_ImplDX10_RenderState //ID3D10SamplerState* SamplerLinear; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerLinear) //ID3D10SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest) }; - -static inline ImGui_ImplDX10_RenderState* ImGui_ImplDX10_GetRenderState() { return (ImGui_ImplDX10_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplDX10_RenderState* ImGui_ImplDX10_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index ff0a607c4..a7d2cdf73 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -103,6 +103,11 @@ static ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplDX11_RenderState* ImGui_ImplDX11_GetRenderState() +{ + return (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11DeviceContext* device_ctx) { diff --git a/backends/imgui_impl_dx11.h b/backends/imgui_impl_dx11.h index 2fe392dfa..5fd8ddca9 100644 --- a/backends/imgui_impl_dx11.h +++ b/backends/imgui_impl_dx11.h @@ -48,7 +48,6 @@ struct ImGui_ImplDX11_RenderState //ID3D11SamplerState* SamplerLinear; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerLinear) //ID3D11SamplerState* SamplerNearest; // Use ImDrawList::AddCallback(ImGui::GetPlatform().DrawCallback_SetSamplerNearest) }; - -static inline ImGui_ImplDX11_RenderState* ImGui_ImplDX11_GetRenderState() { return (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplDX11_RenderState* ImGui_ImplDX11_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index e5b314eb1..22a9bdc97 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -133,6 +133,11 @@ static ImGui_ImplDX12_Data* ImGui_ImplDX12_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX12_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplDX12_RenderState* ImGui_ImplDX12_GetRenderState() +{ + return (ImGui_ImplDX12_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Buffers used during the rendering of a frame struct ImGui_ImplDX12_RenderBuffers { diff --git a/backends/imgui_impl_dx12.h b/backends/imgui_impl_dx12.h index a8f3a6cf6..106b4b4d2 100644 --- a/backends/imgui_impl_dx12.h +++ b/backends/imgui_impl_dx12.h @@ -74,8 +74,7 @@ struct ImGui_ImplDX12_RenderState ID3D12Device* Device; ID3D12GraphicsCommandList* CommandList; }; - -static inline ImGui_ImplDX12_RenderState* ImGui_ImplDX12_GetRenderState() { return (ImGui_ImplDX12_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplDX12_RenderState* ImGui_ImplDX12_GetRenderState(); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy initialization API Obsoleted in 1.91.5 diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 1d340f637..73dc349d8 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -276,6 +276,11 @@ static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState() +{ + return (ImGui_ImplOpenGL3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only) #ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY struct ImGui_ImplOpenGL3_VtxAttribState diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h index b5bcc7400..e1d999bd3 100644 --- a/backends/imgui_impl_opengl3.h +++ b/backends/imgui_impl_opengl3.h @@ -76,7 +76,6 @@ struct ImGui_ImplOpenGL3_RenderState unsigned int CurrentSampler; // (GLuint) Used if UseBindSampler == true (and are available), otherwise always 0 unsigned int CurrentTexParameterFilter; // (GLuint) Used if UseTexParameterFilter == true }; - -static inline ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState() { return (ImGui_ImplOpenGL3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplOpenGL3_RenderState* ImGui_ImplOpenGL3_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 53b47d434..2f6b25015 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -94,6 +94,11 @@ static ImGui_ImplSDLGPU3_Data* ImGui_ImplSDLGPU3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplSDLGPU3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplSDLGPU3_RenderState* ImGui_ImplSDLGPU3_GetRenderState() +{ + return (ImGui_ImplSDLGPU3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + static void ImGui_ImplSDLGPU3_SetupRenderState(ImDrawData* draw_data, SDL_GPUGraphicsPipeline* pipeline, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, ImGui_ImplSDLGPU3_FrameData* fd, uint32_t fb_width, uint32_t fb_height) { ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); diff --git a/backends/imgui_impl_sdlgpu3.h b/backends/imgui_impl_sdlgpu3.h index ce216fef0..a517c43b1 100644 --- a/backends/imgui_impl_sdlgpu3.h +++ b/backends/imgui_impl_sdlgpu3.h @@ -59,7 +59,6 @@ struct ImGui_ImplSDLGPU3_RenderState { SDL_GPUDevice* Device; }; - -static inline ImGui_ImplSDLGPU3_RenderState* ImGui_ImplSDLGPU3_GetRenderState() { return (ImGui_ImplSDLGPU3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplSDLGPU3_RenderState* ImGui_ImplSDLGPU3_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlrenderer2.cpp b/backends/imgui_impl_sdlrenderer2.cpp index e66150e57..d8dcfb154 100644 --- a/backends/imgui_impl_sdlrenderer2.cpp +++ b/backends/imgui_impl_sdlrenderer2.cpp @@ -76,6 +76,11 @@ static ImGui_ImplSDLRenderer2_Data* ImGui_ImplSDLRenderer2_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer2_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplSDLRenderer2_RenderState* ImGui_ImplSDLRenderer2_GetRenderState() +{ + return (ImGui_ImplSDLRenderer2_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions static void ImGui_ImplSDLRenderer2_SetupRenderState(SDL_Renderer* renderer) { diff --git a/backends/imgui_impl_sdlrenderer2.h b/backends/imgui_impl_sdlrenderer2.h index 0c14912db..e16b8cae9 100644 --- a/backends/imgui_impl_sdlrenderer2.h +++ b/backends/imgui_impl_sdlrenderer2.h @@ -51,7 +51,6 @@ struct ImGui_ImplSDLRenderer2_RenderState { SDL_Renderer* Renderer; }; - -static inline ImGui_ImplSDLRenderer2_RenderState* ImGui_ImplSDLRenderer2_GetRenderState() { return (ImGui_ImplSDLRenderer2_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplSDLRenderer2_RenderState* ImGui_ImplSDLRenderer2_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdlrenderer3.cpp b/backends/imgui_impl_sdlrenderer3.cpp index 121ce59c8..6dd4c14fe 100644 --- a/backends/imgui_impl_sdlrenderer3.cpp +++ b/backends/imgui_impl_sdlrenderer3.cpp @@ -81,6 +81,11 @@ static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() +{ + return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + // Functions static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer) { diff --git a/backends/imgui_impl_sdlrenderer3.h b/backends/imgui_impl_sdlrenderer3.h index 019def26a..ee701be38 100644 --- a/backends/imgui_impl_sdlrenderer3.h +++ b/backends/imgui_impl_sdlrenderer3.h @@ -51,7 +51,6 @@ struct ImGui_ImplSDLRenderer3_RenderState SDL_Renderer* Renderer; SDL_ScaleMode CurrentScaleMode; // Current scale mode during render. Set to SDL_SCALEMODE_INVALID to use "SetSamplerFromTex" mode. }; - -static inline ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState() { return (ImGui_ImplSDLRenderer3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplSDLRenderer3_RenderState* ImGui_ImplSDLRenderer3_GetRenderState(); #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 9eca9a767..80b9d58ab 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -459,6 +459,11 @@ static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplVulkan_RenderState* ImGui_ImplVulkan_GetRenderState() +{ + return (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + #ifdef IMGUI_IMPL_VULKAN_USE_LOADER // Get the Vulkan function pointers used by the current backend. // If no backend is available, return the currently loaded global function pointers (ImGuiImplVulkanFuncs). diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index a48dae3ec..0f2da5e2d 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -175,8 +175,7 @@ struct ImGui_ImplVulkan_RenderState VkPipeline Pipeline; VkPipelineLayout PipelineLayout; }; - -static inline ImGui_ImplVulkan_RenderState* ImGui_ImplVulkan_GetRenderState() { return (ImGui_ImplVulkan_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplVulkan_RenderState* ImGui_ImplVulkan_GetRenderState(); //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index bdc4d434b..002967b92 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -129,6 +129,11 @@ static ImGui_ImplWGPU_Data* ImGui_ImplWGPU_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplWGPU_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +ImGui_ImplWGPU_RenderState* ImGui_ImplWGPU_GetRenderState() +{ + return (ImGui_ImplWGPU_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; +} + //----------------------------------------------------------------------------- // SHADERS //----------------------------------------------------------------------------- diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index a5867c923..9f206db32 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -81,8 +81,7 @@ struct ImGui_ImplWGPU_RenderState WGPUDevice Device; WGPURenderPassEncoder RenderPassEncoder; }; - -static inline ImGui_ImplWGPU_RenderState* ImGui_ImplWGPU_GetRenderState() { return (ImGui_ImplWGPU_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState; } +IMGUI_IMPL_API ImGui_ImplWGPU_RenderState* ImGui_ImplWGPU_GetRenderState(); //------------------------------------------------------------------------- // Internal Helpers From d5c68c301a3d564e54051a7a78ee7858c4b821fb Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2026 14:16:55 +0200 Subject: [PATCH 240/254] ImGuiTextFilter: reworked filter to make space an AND operator. Demo: tweaked demo. Removed FilterOp, MinWordSize added a few commits ago. --- docs/CHANGELOG.txt | 6 ++-- imgui.cpp | 78 ++++++++++++++++++++++++++++------------------ imgui.h | 14 ++++----- imgui_demo.cpp | 23 +++++++++----- 4 files changed, 73 insertions(+), 48 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 82c2ffe00..ca1e27e49 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -84,8 +84,10 @@ Other Changes: hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - ImGuiTextFilter: - - Added support for space ' ' as separator, which is more standard. (#2435, #30) - - Added support for quoted "xxx" blocks to e.g. match words sequences. (#2435) + - Added support for space as an "and" filter, which is the standard. (#2435, #30) + e.g. w/ filter 'hello world' (without quotes) both words needs to be in input data. + - Added support for double-quoted "xxx" blocks to e.g. match words sequences. (#2435) + e.g. w/ filter "hello world" the exact sequence needs to be in input data. - Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" match always filters out. (#2435) [@kudaba, @ocornut] - Added `DrawWithHint()` function. (#6206, #6395, #6447) diff --git a/imgui.cpp b/imgui.cpp index f9dc89e53..7e353df8f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3094,9 +3094,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { InputBuf[0] = 0; - FilterOp = '|'; - MinWordSize = 1; - _CountExclude = _CountInclude = 0; + _CountExclude = 0; if (default_filter) { ImStrncpy(InputBuf, default_filter, IM_COUNTOF(InputBuf)); @@ -3118,14 +3116,15 @@ bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint) return value_changed; } -// Parse filter and split into items +// Parse filter and split into a format optimal for PassFilter() void ImGuiTextFilter::Build() { _Items.resize(0); - _CountExclude = _CountInclude = 0; - IM_ASSERT(FilterOp == '|' || FilterOp == '&'); + _CountExclude = 0; + const char* buf_e = InputBuf + ImStrlen(InputBuf); const char* word_e; + int seq_incl_start_idx = -1; for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1) { // Trim blanks @@ -3151,20 +3150,33 @@ void ImGuiTextFilter::Build() } // Min length - if (word_e - word_b < MinWordSize) - continue; + if (word_e - word_b > 0) + { + // Add to list. The '-' is not stored in items but implicitly inferred using (n < CountExclude). + // FIXME-OPT: as items are derived from user input buffer and we expect the insert() to behave sanely. + if (is_excl) + { + _Items.insert(_Items.Data + _CountExclude, ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); + _CountExclude++; + if (seq_incl_start_idx != -1) + seq_incl_start_idx++; + } + else + { + if (seq_incl_start_idx == -1) + seq_incl_start_idx = _Items.Size; + _Items.insert(_Items.Data + _Items.Size, ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); + _Items.Data[seq_incl_start_idx].CountInclude++; + } + } - // Add to list - // The '-' is not stored in items but implicitly inferred using (n < CountExclude). - // FIXME-OPT: about ~push_front(): as N is derived from user inputs we expect this to be fine. - _Items.insert(is_excl ? _Items.Data : _Items.Data + _Items.Size, ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); - if (is_excl) - _CountExclude++; - else - _CountInclude++; + // Next sequence + if (word_e[0] == ',') + seq_incl_start_idx = -1; } } +// FIXME: Could use a specialized ImStristr() + pre-convert our filter to uppercase. bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const { if (_Items.Size == 0) @@ -3173,24 +3185,28 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const text = text_end = ""; // Filters are sorted so that '-' ones are always leading. - int n; - for (n = 0; n < _CountExclude; n++) - if (ImStristr(text, text_end, _Items.Data[n].Begin, _Items.Data[n].End) != NULL) + ImGuiTextFilterItem* seq = _Items.Data; + ImGuiTextFilterItem* seq_excl_end = _Items.Data + _CountExclude; + for (; seq < seq_excl_end; seq++) + if (ImStristr(text, text_end, seq->Begin, seq->Begin + seq->Len) != NULL) return false; - const bool is_and_filter = (FilterOp == '&'); - for (; n < _Items.Size; n++) - { - const bool is_match = ImStristr(text, text_end, _Items.Data[n].Begin, _Items.Data[n].End) != NULL; - if (is_match && !is_and_filter) // or incl 1 -> true - return true; // or incl 0 -> continue - if (!is_match && is_and_filter) // and incl 1 -> continue - return false; // and incl 0 -> false - } - // When no inclusion are specified (only exclusions) we implicitly pass - if (_CountInclude == 0) + // Process includes + ImGuiTextFilterItem* seq_incl_end = _Items.Data + _Items.Size; + if (seq == seq_incl_end) // When no inclusion are specified (only exclusions) we implicitly pass return true; - return is_and_filter; + while (seq < seq_incl_end) + { + ImGuiTextFilterItem* seq_next = seq + seq->CountInclude; + IM_ASSERT_PARANOID(seq->CountInclude > 0 && seq_next <= seq_incl_end); + for (; seq < seq_next; seq++) + if (ImStristr(text, text_end, seq->Begin, seq->Begin + seq->Len) == NULL) + break; + if (seq == seq_next) // All matched + return true; + seq = seq_next; // Try next + } + return false; } //----------------------------------------------------------------------------- diff --git a/imgui.h b/imgui.h index 2ad3b91fb..1e0ce7dcc 100644 --- a/imgui.h +++ b/imgui.h @@ -2785,7 +2785,7 @@ struct ImGuiOnceUponAFrame operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } }; -// Helper: Parse and apply text filters e.g. 'aaa bbb -ccc'. +// Helper: Parse and apply text filters e.g. 'aaa bbb' (all), 'aaa,bbb' (any), '-aaa' (exclude), '"Hello, world"' (exact sequence) struct ImGuiTextFilter { IMGUI_API ImGuiTextFilter(const char* default_filter = ""); @@ -2801,20 +2801,18 @@ struct ImGuiTextFilter inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); } #endif - // [Internal] Don't use! Will be replaced with ImStrv. + // [Internal] Types struct ImGuiTextFilterItem { const char* Begin; - const char* End; - ImGuiTextFilterItem(const char* b, const char* e) { Begin = b; End = e; } + int Len; + int CountInclude; // >0 when beginning of an AND chain. + ImGuiTextFilterItem(const char* b, const char* e) { IM_ASSERT(e > b); Begin = b; Len = (int)(e - b); CountInclude = 0; } }; // [Internal] Members char InputBuf[256]; // User input buffer - char FilterOp; // == '|' (any) pr '&' (all) - ImU8 MinWordSize; // == 1 - int _CountExclude; // >= 0 - int _CountInclude; // >= 0 + int _CountExclude; // >= 0 count of leading exclude ImVector _Items; // Pre-parsed, trimmed, reordered items }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5ef47f3af..0b16dab3c 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3943,15 +3943,24 @@ static void DemoWindowWidgetsTextFilter() static ImGuiTextFilter filter; ImGui::Text("Filter usage:\n" " \"\" display all lines\n" - " \"xxx\" display lines containing \"xxx\"\n" - " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" - " \"-xxx\" hide lines containing \"xxx\""); + " xxx display lines containing \"xxx\"\n" + " xxx yyy display lines containing \"xxx\" and \"yyy\"\n" + " \"xxx yyy\" display lines containing \"xxx yyy\"\n" + " xxx,yyy display lines containing \"xxx\" or \"yyy\"\n" + " -xxx hide lines containing \"xxx\""); ImGui::SetNextItemWidth(-FLT_MIN); filter.DrawWithHint("##Filter", "Filter (incl -excl)"); - const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (int i = 0; i < IM_COUNTOF(lines); i++) - if (filter.PassFilter(lines[i])) - ImGui::BulletText("%s", lines[i]); + if (ImGui::BeginChild("##items", ImVec2(-FLT_MIN, ImGui::GetTextLineHeightWithSpacing() * 15), ImGuiChildFlags_FrameStyle)) + { + const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; + for (const char* item : lines) + if (filter.PassFilter(item)) + ImGui::TextUnformatted(item); + for (const char* item : ExampleNames) + if (filter.PassFilter(item)) + ImGui::TextUnformatted(item); + } + ImGui::EndChild(); ImGui::TreePop(); } } From e19fea020f69e97e420f32d80f8f1dc7e829dc2c Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2026 15:12:59 +0200 Subject: [PATCH 241/254] ImGuiTextFilter: moved ImGuiTextFilterItem to internals. (#2435) --- imgui.cpp | 4 ++-- imgui.h | 16 ++++------------ imgui_internal.h | 10 ++++++++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7e353df8f..b3cb17a17 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3156,7 +3156,7 @@ void ImGuiTextFilter::Build() // FIXME-OPT: as items are derived from user input buffer and we expect the insert() to behave sanely. if (is_excl) { - _Items.insert(_Items.Data + _CountExclude, ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); + _Items.insert(_Items.Data + _CountExclude, ImGuiTextFilterItem(word_b, word_e)); _CountExclude++; if (seq_incl_start_idx != -1) seq_incl_start_idx++; @@ -3165,7 +3165,7 @@ void ImGuiTextFilter::Build() { if (seq_incl_start_idx == -1) seq_incl_start_idx = _Items.Size; - _Items.insert(_Items.Data + _Items.Size, ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); + _Items.insert(_Items.Data + _Items.Size, ImGuiTextFilterItem(word_b, word_e)); _Items.Data[seq_incl_start_idx].CountInclude++; } } diff --git a/imgui.h b/imgui.h index 1e0ce7dcc..a108888a2 100644 --- a/imgui.h +++ b/imgui.h @@ -2786,6 +2786,7 @@ struct ImGuiOnceUponAFrame }; // Helper: Parse and apply text filters e.g. 'aaa bbb' (all), 'aaa,bbb' (any), '-aaa' (exclude), '"Hello, world"' (exact sequence) +struct ImGuiTextFilterItem; struct ImGuiTextFilter { IMGUI_API ImGuiTextFilter(const char* default_filter = ""); @@ -2801,19 +2802,10 @@ struct ImGuiTextFilter inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); } #endif - // [Internal] Types - struct ImGuiTextFilterItem - { - const char* Begin; - int Len; - int CountInclude; // >0 when beginning of an AND chain. - ImGuiTextFilterItem(const char* b, const char* e) { IM_ASSERT(e > b); Begin = b; Len = (int)(e - b); CountInclude = 0; } - }; - - // [Internal] Members + // Members char InputBuf[256]; // User input buffer - int _CountExclude; // >= 0 count of leading exclude - ImVector _Items; // Pre-parsed, trimmed, reordered items + int _CountExclude; // [Internal] >= 0 count of leading exclude + ImVector _Items; // [Internal] Pre-parsed, trimmed, reordered items }; // Helper: Growable text buffer for logging/accumulating text diff --git a/imgui_internal.h b/imgui_internal.h index 8b6ffb796..15d2513aa 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -368,6 +368,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex +// - Helper: ImGuiTextFilterItem // - Helper: ImGuiStorage //----------------------------------------------------------------------------- @@ -836,6 +837,15 @@ struct ImGuiTextIndex void append(const char* base, int old_size, int new_size); }; +// [Internal] Types +struct ImGuiTextFilterItem +{ + const char* Begin; + int Len; + int CountInclude; // >0 when beginning of an AND chain. + ImGuiTextFilterItem(const char* b, const char* e) { IM_ASSERT(e > b); Begin = b; Len = (int)(e - b); CountInclude = 0; } +}; + // Helper: ImGuiPackedDate (sizeof() == 2) // Store a date in a way that is efficient to read/write in text form. If we stored e.g. number of days since Epoch we'd need costlier back and forth. // This is specifically designed to be able to prune old .ini data. From 6cf8332f93ac2acbba03112171d44396730a74f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2026 16:52:48 +0200 Subject: [PATCH 242/254] ImGuiTextFilterItem: added MatchIncludesMask support field for e.g. ImGuiTextFilterMultiMatch. --- imgui_internal.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 15d2513aa..77fd38256 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -840,10 +840,11 @@ struct ImGuiTextIndex // [Internal] Types struct ImGuiTextFilterItem { - const char* Begin; - int Len; - int CountInclude; // >0 when beginning of an AND chain. - ImGuiTextFilterItem(const char* b, const char* e) { IM_ASSERT(e > b); Begin = b; Len = (int)(e - b); CountInclude = 0; } + const char* Begin; // Pointer within parent's buffer + int Len; // Length of text (non-zero terminated) + short CountInclude; // Number of consecutive items forming an end chain. >0 when beginning of an AND chain, =0 otherwise. + unsigned short MatchIncludesMask; // Storage for multi-sources versions of PassFilter(). Unused by core library. + ImGuiTextFilterItem(const char* b, const char* e) { IM_ASSERT(e > b); Begin = b; Len = (int)(e - b); CountInclude = 0; MatchIncludesMask = 0; } }; // Helper: ImGuiPackedDate (sizeof() == 2) From 58d7570b8bbbfdcade40799812da9c7162e13498 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2026 18:23:26 +0200 Subject: [PATCH 243/254] ImGuiTextFilterItem: shallow renames. --- imgui.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b3cb17a17..7bbfa72bd 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3124,7 +3124,7 @@ void ImGuiTextFilter::Build() const char* buf_e = InputBuf + ImStrlen(InputBuf); const char* word_e; - int seq_incl_start_idx = -1; + int seq_start_idx = -1; for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1) { // Trim blanks @@ -3158,21 +3158,21 @@ void ImGuiTextFilter::Build() { _Items.insert(_Items.Data + _CountExclude, ImGuiTextFilterItem(word_b, word_e)); _CountExclude++; - if (seq_incl_start_idx != -1) - seq_incl_start_idx++; + if (seq_start_idx != -1) + seq_start_idx++; } else { - if (seq_incl_start_idx == -1) - seq_incl_start_idx = _Items.Size; + if (seq_start_idx == -1) + seq_start_idx = _Items.Size; _Items.insert(_Items.Data + _Items.Size, ImGuiTextFilterItem(word_b, word_e)); - _Items.Data[seq_incl_start_idx].CountInclude++; + _Items.Data[seq_start_idx].CountInclude++; } } // Next sequence if (word_e[0] == ',') - seq_incl_start_idx = -1; + seq_start_idx = -1; } } @@ -3192,19 +3192,19 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const return false; // Process includes - ImGuiTextFilterItem* seq_incl_end = _Items.Data + _Items.Size; - if (seq == seq_incl_end) // When no inclusion are specified (only exclusions) we implicitly pass + ImGuiTextFilterItem* seq_end = _Items.Data + _Items.Size; + if (seq == seq_end) // When no inclusion are specified (only exclusions) we implicitly pass return true; - while (seq < seq_incl_end) + while (seq < seq_end) { - ImGuiTextFilterItem* seq_next = seq + seq->CountInclude; - IM_ASSERT_PARANOID(seq->CountInclude > 0 && seq_next <= seq_incl_end); - for (; seq < seq_next; seq++) + ImGuiTextFilterItem* seq_incl_end = seq + seq->CountInclude; + IM_ASSERT_PARANOID(seq->CountInclude > 0 && seq_incl_end <= seq_end); + for (; seq < seq_incl_end; seq++) if (ImStristr(text, text_end, seq->Begin, seq->Begin + seq->Len) == NULL) break; - if (seq == seq_next) // All matched + if (seq == seq_incl_end) // All matched return true; - seq = seq_next; // Try next + seq = seq_incl_end; // Try next } return false; } From d12321803d168099245da64fbe57e7f767f744b2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2026 15:57:40 +0200 Subject: [PATCH 244/254] Popups: reworked GetMousePosOnOpeningCurrentPopup() to be legal to call within the BeginPopup/EndPopup scope of a popup which was just closed. (#9551) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ca1e27e49..07cf3ec9c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -83,6 +83,8 @@ Other Changes: - Amended context menu right-click opening code to check that the right-click hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) + - Reworked `GetMousePosOnOpeningCurrentPopup()` to be legal to call within + the BeginPopup/EndPopup scope of a popup which was just closed. (#9551) [@jkunstwald] - ImGuiTextFilter: - Added support for space as an "and" filter, which is the standard. (#2435, #30) e.g. w/ filter 'hello world' (without quotes) both words needs to be in input data. diff --git a/imgui.cpp b/imgui.cpp index 7bbfa72bd..f7d9bfa91 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10269,12 +10269,11 @@ void ImGui::TeleportMousePos(const ImVec2& pos) //IMGUI_DEBUG_LOG_IO("TeleportMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); } -// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() { ImGuiContext& g = *GImGui; if (g.BeginPopupStack.Size > 0) - return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos; + return g.BeginPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos; return g.IO.MousePos; } From 7bfc19281459c67863d4615c56152567bb98c773 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2026 16:48:51 +0200 Subject: [PATCH 245/254] Tables: tweak struct packing. --- docs/CHANGELOG.txt | 4 ++-- imgui_internal.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 07cf3ec9c..ff7c4d265 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -83,8 +83,8 @@ Other Changes: - Amended context menu right-click opening code to check that the right-click hasn't been owned by another items. This is more correct and also necessary to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - - Reworked `GetMousePosOnOpeningCurrentPopup()` to be legal to call within - the BeginPopup/EndPopup scope of a popup which was just closed. (#9551) [@jkunstwald] + - Reworked `GetMousePosOnOpeningCurrentPopup()` to be legal to call within the + BeginPopup/EndPopup scope of a popup which was just closed. (#9551) [@jkunstwald] - ImGuiTextFilter: - Added support for space as an "and" filter, which is the standard. (#2435, #30) e.g. w/ filter 'hello world' (without quotes) both words needs to be in input data. diff --git a/imgui_internal.h b/imgui_internal.h index 77fd38256..17925f2f8 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3137,9 +3137,9 @@ struct IMGUI_API ImGuiTable ImDrawListSplitter* DrawSplitter; // Shortcut to TempData->DrawSplitter while in table. Isolate draw commands per columns to avoid switching clip rect constantly ImGuiTableInstanceData InstanceDataFirst; ImVector InstanceDataExtra; // FIXME-OPT: Using a small-vector pattern would be good. - ImGuiTableColumnSortSpecs SortSpecsSingle; - ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() + ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. + ImGuiTableColumnSortSpecs SortSpecsSingle; ImGuiTableColumnIdx SortSpecsCount; ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount) ImGuiTableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns using fixed width (<= ColumnsCount) From a6784c378fb59d0cdee51b08b09fbd8ec9d01d95 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2026 16:54:13 +0200 Subject: [PATCH 246/254] Tables: fixed reconcile path being needlessly taken on first frame of every table. (#9108) --- imgui_internal.h | 1 + imgui_tables.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 17925f2f8..97158497b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3171,6 +3171,7 @@ struct IMGUI_API ImGuiTable ImS8 NavLayer; // ImGuiNavLayer at the time of BeginTable(). bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). + bool IsNewTable; bool IsInitializing; bool IsReconcileMode; bool IsSortSpecsDirty; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index fb1b998d0..eba5b12e2 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -375,7 +375,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG const int previous_frame_active = table->LastFrameActive; const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1; const ImGuiTableFlags previous_flags = table->Flags; - const bool is_new_table = (previous_frame_active == -1); table->ID = id; table->Flags = flags; table->LastFrameActive = g.FrameCount; @@ -384,6 +383,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->IsLayoutLocked = false; table->InnerWidth = inner_width; table->NavLayer = (ImS8)outer_window->DC.NavLayerCurrent; + table->IsNewTable = (previous_frame_active == -1); temp_data->UserOuterSize = outer_size; // Instance data (for instance 0, TableID == TableInstanceID) @@ -585,7 +585,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsInitializing) { // Initialize - if (is_new_table) + if (table->IsNewTable) { table->SettingsOffset = -1; table->IsSettingsRequestLoad = true; @@ -1756,7 +1756,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo ImGuiTableColumn* column = &table->Columns[column_idx]; // If topology change goes into reconcile mode - if (table->IsReconcileMode == false && column->ID != column_id) + if (table->IsReconcileMode == false && column->ID != column_id && !table->IsNewTable) { table->IsReconcileMode = true; table->TempData->ReconcileColumnsRequests.reserve(table->ColumnsCount - column_idx); From cc072ca90e525c0793ff80a152be8eeb70937261 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2026 17:13:16 +0200 Subject: [PATCH 247/254] Tables: comments for saving fix when using ImGuiTableFlags_Sortable and no user changes were made. (#9519, #9108) Because SortOrder save is gated, it doesn't guarantee every column gets an .ini entry. Amend 1d7b37dc9b. We set ImGuiTableFlags_Reorderable but technically any other field would work, tho ImGuiTableFlags_Reorderable makes more sense. --- docs/CHANGELOG.txt | 3 +++ imgui_tables.cpp | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ff7c4d265..a1170fcde 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -68,6 +68,9 @@ Other Changes: - Using <> scrolling buttons while the selected Tab is at either end of the section mid-section but not fully visible always makes it fully visible (before eventually selecting a Tab in the leading/trailing section) +- Tables: + - Fixed a bug restoring correct .ini columns order when table use is Sortable + but had no user changes. (regression from 1.92.9). (#9519, #9108) - Selectable: - Fixed `ImGuiSelectableFlags_SelectOnNav` triggering during NavInit. - Fonts: diff --git a/imgui_tables.cpp b/imgui_tables.cpp index eba5b12e2..b3e0458eb 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3970,12 +3970,10 @@ void ImGui::TableSaveSettings(ImGuiTable* table) if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column->SortOrder != -1) - settings->SaveFlags |= ImGuiTableFlags_Sortable; + settings->SaveFlags |= ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable; // Because SortOrder saving itself is gated, make sure every column is saved (#9519) if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; } - if (table->Flags & ImGuiTableFlags_Sortable) - settings->SaveFlags |= ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable; settings->SaveFlags &= table->Flags; settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; From 623ba461202d73471d65a1c2b608eecc3959a6d6 Mon Sep 17 00:00:00 2001 From: Daniel Castonguay Date: Tue, 22 Sep 2026 18:12:59 -0400 Subject: [PATCH 248/254] DrawList: fixed dangling ImDrawList::_OwnerName when using animated label. (#9552) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a1170fcde..ceb094151 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -124,6 +124,7 @@ Other Changes: - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic draw callback for requesting to use the sampler associated already associated to a texture. Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) + - Fixed dangling ImDrawList::_OwnerName when using animated label. (#9552) [@ubi-dcastonguay] - Demo: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Style Editor: fixed style/colors combo box applying style when opening Combo. diff --git a/imgui.cpp b/imgui.cpp index f7d9bfa91..2bc9e4c34 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7755,6 +7755,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) size_t buf_len = (size_t)window->NameBufLen; window->Name = ImStrdupcpy(window->Name, &buf_len, name); window->NameBufLen = (int)buf_len; + window->DrawList->_OwnerName = window->Name; } // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS From e7289367973e97012a247d6b0a6c4c31b2d4216c Mon Sep 17 00:00:00 2001 From: Brenton Bostick Date: Thu, 24 Sep 2026 07:48:14 -0400 Subject: [PATCH 249/254] Docs: fixed typos/repeated words. (#9555) --- backends/imgui_impl_opengl3_loader.h | 2 +- docs/CHANGELOG.txt | 8 ++++---- imgui.cpp | 4 ++-- imgui_internal.h | 4 ++-- imgui_widgets.cpp | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backends/imgui_impl_opengl3_loader.h b/backends/imgui_impl_opengl3_loader.h index 2c485841a..34b262dff 100644 --- a/backends/imgui_impl_opengl3_loader.h +++ b/backends/imgui_impl_opengl3_loader.h @@ -114,7 +114,7 @@ extern "C" { #define GLAPI extern #endif /* glcorearb.h is for use with OpenGL core profile implementations. -** It should should be placed in the same directory as gl.h and +** It should be placed in the same directory as gl.h and ** included as . ** ** glcorearb.h includes only APIs in the latest OpenGL core profile diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ceb094151..d39b8c7b2 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -145,7 +145,7 @@ Other Changes: don't have the equivalent of `SDL_SetTextureScaleMode()` and the callback won't work with them. - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` - when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] + when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] - Examples: - MSVC: disabled /JMC in Visual Studio projects (unnecessary overhead). @@ -1755,7 +1755,7 @@ Non-breaking Fonts/Textures related changes: - Fonts: ImFontAtlas::AddFontXXX() can fail more gracefully if error handling is configured to not assert (this will be better exposed via future font flags). - Fonts: added style.FontScaleBase scaling factor (previously called io.FontGlobalScale). -- Fonts: added style.FontScaleDpi scaling factor. This is designed to be be changed on +- Fonts: added style.FontScaleDpi scaling factor. This is designed to be changed on per-monitor/per-viewport basis, which `io.ConfigDpiScaleFonts` does automatically. (which is why it is separate from FontScaleBase). - Fonts: added optional font_size parameter to ImGui::PushFont() function. @@ -2910,7 +2910,7 @@ Other changes: - Backends: Vulkan: Remove Volk/ from volk.h #include directives. (#7722, #6582, #4854) [@martin-ejdestig] - Examples: SDL3: Remove use of SDL_HINT_IME_NATIVE_UI since new SDL_HINT_IME_IMPLEMENTED_UI - values has a more suitable default for our case case. + values has a more suitable default for our case. - Examples: GLFW+Vulkan, SDL+Vulkan: handle swap chain resize even without Vulkan returning VK_SUBOPTIMAL_KHR, which doesn't seem to happen on Wayland. (#7671) [@AndreiNego, @ocornut] @@ -4863,7 +4863,7 @@ Other Changes: - ColorPicker4: Fixed picker being unable to select exact 1.0f color when dragging toward the edges of the SV square (previously picked 0.999989986f). (#3517) [@rokups] - Menus: Fixed vertical alignments of MenuItem() calls within a menu bar (broken in 1.84). (#4538) -- Menus: Improve closing logic when moving diagonally in empty between between parent and child menus to +- Menus: Improve closing logic when moving diagonally in empty space between parent and child menus to accommodate for varying font size and dpi. - Menus: Fixed crash when navigating left inside a child window inside a sub-menu. (#4510). - Menus: Fixed an assertion happening in some situations when closing nested menus (broken in 1.83). (#4640) diff --git a/imgui.cpp b/imgui.cpp index 2bc9e4c34..d25365f16 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -230,7 +230,7 @@ CODE This is to increase compatibility, increase maintainability and facilitate use from other languages. - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types. See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that. - We can can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use internally. + We can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use internally. - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction (so don't use ImVector in your code or at our own risk!). - Building: We don't use nor mandate a build system for the main library. @@ -12859,7 +12859,7 @@ bool ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags // This is a helper to handle the simplest case of associating one named popup to one given widget. // - To create a popup associated to the last item, you generally want to pass a NULL value to str_id. // - To create a popup with a specific identifier, pass it in str_id. -// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. +// - This is useful when using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. // - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id. // - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). // This is essentially the same as: diff --git a/imgui_internal.h b/imgui_internal.h index 97158497b..677a257c6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -538,7 +538,7 @@ inline float ImFloor(float f) { return inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } inline float ImTrunc64(float f) { return (float)(ImS64)(f); } inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only. -inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the the bit-hack version (search for "0x1p120f"). +inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the bit-hack version (search for "0x1p120f"). inline int ImModPositive(int a, int b) { return (a + b) % b; } inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } @@ -905,7 +905,7 @@ struct IMGUI_API ImDrawListSharedData const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) - float FontSize; // Current font size (used for for simplified AddText overload) + float FontSize; // Current font size (used for simplified AddText overload) float FontScale; // Current font scale (== FontSize / Font->FontSize) float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1f60853b6..7737a41e6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4795,7 +4795,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) - IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline does not not work with left-trimming + IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline does not work with left-trimming IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Password) == 0); // WordWrap does not work with Password mode. IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Multiline) != 0); // WordWrap does not work in single-line mode. From 5c6a86a9d27d04d4274f7b25be24b38a4579facd Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 25 Sep 2026 14:07:59 +0200 Subject: [PATCH 250/254] imgui_freetype: fixed ExtraSizeScale (used by embedded ProggyForever) leading to non-pixel aligned vertical offset. (#9348, #9233) + Added ImRoundPositive64(), ImRoundSigned64(). --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 2 +- imgui_draw.cpp | 1 + imgui_internal.h | 3 ++- misc/freetype/imgui_freetype.cpp | 3 +++ 5 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d39b8c7b2..e751035ac 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -77,6 +77,8 @@ Other Changes: - Reworked `AddFontDefault()` to use `io.DisplayFrameBufferScale` as part of the heuristic to select `AddFontDefaultVector()` by default, effectively using ProggyForever instead of ProggyClean on most Apple/Retina setups by default. + - imgui_freetype: Fixed blurryness rendering `AddFontDefaultVector()`. Caused by a + non-pixel aligned offset caused by `ExtraSizeScale`. (#9348, #9233) [@redpartizan, @RDMCz] - ColorEdit: - ColorButton, ColorEdit, ColorPicker: cancel-out alpha caused by BeginDisabled() so disabled color buttons have the same color as non-disabled oness. (#9511) diff --git a/imgui.cpp b/imgui.cpp index d25365f16..57ed579af 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12080,7 +12080,7 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) } scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll[axis] = ImRound64(ImMax(scroll[axis], 0.0f)); + scroll[axis] = ImRoundPositive64(ImMax(scroll[axis], 0.0f)); if (!window->Collapsed && !window->SkipItems) scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0e76fcefc..a4097bf38 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4776,6 +4776,7 @@ static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); baked->Descent = ImFloor(unscaled_descent * scale_for_layout); + // FIXME: round to rasterizer density? (#9348) } return true; } diff --git a/imgui_internal.h b/imgui_internal.h index 677a257c6..0ffadedc3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -537,7 +537,8 @@ inline ImVec2 ImTrunc(const ImVec2& v) { return inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } inline float ImTrunc64(float f) { return (float)(ImS64)(f); } -inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } // FIXME: Positive values only. +inline float ImRoundPositive64(float f) { return (float)(ImS64)(f + 0.5f); } // Positive values only. +inline float ImRoundSigned64(float f) { return (float)(ImS64)(f >= 0.0f ? f + 0.5f : f - 0.5f); } inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the bit-hack version (search for "0x1p120f"). inline int ImModPositive(int a, int b) { return (a + b) % b; } inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 79dff8bfb..83259bfd0 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -7,6 +7,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026/09/25: fixed handling of ExtraSizeScale. // 2025/06/11: refactored for the new ImFontLoader architecture, and ImGuiBackendFlags_RendererHasTextures support. // 2024/10/17: added plutosvg support for SVG Fonts (seems faster/better than lunasvg). Enable by using '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG'. (#7927) // 2023/11/13: added support for ImFontConfig::RasterizationDensity field for scaling render density without scaling metrics. @@ -468,6 +469,8 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. //LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent. //MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font. + baked->Ascent = ImRoundSigned64(baked->Ascent * rasterizer_density) / rasterizer_density; + baked->Descent = ImRoundSigned64(baked->Descent * rasterizer_density) / rasterizer_density; } return true; } From aa0181478b182f7170378a5d8629401b77ee326e Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 25 Sep 2026 14:17:14 +0200 Subject: [PATCH 251/254] imgui_freetype: tweak rounding code. (#9348, #9233) --- misc/freetype/imgui_freetype.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 83259bfd0..2e2e78f7d 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -464,8 +464,8 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s // Read metrics FT_Size_Metrics metrics = bd_baked_data->FtSize->metrics; const float scale = 1.0f / (rasterizer_density * src->ExtraSizeScale); - baked->Ascent = (float)FT_CEIL(metrics.ascender) * scale; // The pixel extents above the baseline in pixels (typically positive). - baked->Descent = (float)FT_CEIL(metrics.descender) * scale; // The extents below the baseline in pixels (typically negative). + baked->Ascent = ((float)metrics.ascender / FT_SCALEFACTOR) * scale; // The pixel extents above the baseline in pixels (typically positive). + baked->Descent = ((float)metrics.descender / FT_SCALEFACTOR) * scale; // The extents below the baseline in pixels (typically negative). //LineSpacing = (float)FT_CEIL(metrics.height) * scale; // The baseline-to-baseline distance. Note that it usually is larger than the sum of the ascender and descender taken as absolute values. There is also no guarantee that no glyphs extend above or below subsequent baselines when using this distance. Think of it as a value the designer of the font finds appropriate. //LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender) * scale; // The spacing in pixels between one row's descent and the next row's ascent. //MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance) * scale; // This field gives the maximum horizontal cursor advance for all glyphs in the font. From 09f7a0f062b902dc0b377439421abc58d369b1d9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 28 Sep 2026 15:12:49 +0200 Subject: [PATCH 252/254] Docs: tweak PR template. --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8061997cf..998b25427 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,12 +1,12 @@ (Click "Preview" to turn any http URL into a clickable link) -1. PLEASE CAREFULLY READ: [Contributing Guidelines](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md) +1. PLEASE CAREFULLY READ: [Contributing Guidelines](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md). By submitting a PR you are agreeing with the [CLA](https://github.com/ocornut/imgui/blob/master/docs/CONTRIBUTING.md#copyright--contributor-license-agreement). 2. **Make sure you're using a special branch just for this pull request**. (In git, 1 PR = 1 branch. If you update the branch the PR will be updated.) 3. Consider running the [imgui_test_suite](https://github.com/ocornut/imgui_test_engine) or adding new tests to test for expected behaviors. -4. PLEASE DO NOT SUBMIT AI SLOP OR AUTOMATED AI REPORTS. Submissions that are lacking proper human review/judgement before being passed to us are not welcome. Do not let a LLM AI write most of the text for you. +4. PLEASE DO NOT SUBMIT AI-GENERATED PROSE. ONLY SUBMIT CODE THAT YOU REVIEWED AND UNDERSTAND. Submissions that are lacking proper human review/judgement before being passed to us are not welcome. 5. Clear this template before submitting your PR. From db436a15bc04cabaa10d8c7ce5e9dc81c3587f8a Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Sat, 26 Sep 2026 12:34:24 +0200 Subject: [PATCH 253/254] Misc: fixed strict aliasing violation in ImHashData() affecting Clang 23 + SSE 4.2 + -flto. (#9557, #9560) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e751035ac..1eee0b79b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -114,6 +114,9 @@ Other Changes: trivial functions when stepping into. - Tagged various trivial functions using `IM_NODEBUGSTEP`: `ImVec2()`, `ImVec4()` etc. and various operators. + - Fixed strict aliasing violation in `ImHashData()` affecting Clang 23 + SSE 4.2 + -flto. + Could manifest as `ImFontAtlasBakedGetId()` computation omitting font size from + the hashed data. (#9557, #9560) [@sthalik] - IO: restore default clipboard/ime/shell platform handlers on ClearPlatformHandlers(), fixing issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] - DrawList: diff --git a/imgui.cpp b/imgui.cpp index 57ed579af..fb86370b9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2493,7 +2493,13 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) #else while (data + 4 <= data_end) { +#if defined(_DEBUG) || defined(_MSC_VER) crc = _mm_crc32_u32(crc, *(ImU32*)data); +#else + ImU32 v; + memcpy(&v, data, sizeof(ImU32)); // Avoid aliasing violation (#9557) + crc = _mm_crc32_u32(crc, v); +#endif data += 4; } while (data < data_end) From 580c00c316285de8104a8fa0701265557fb78a62 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 28 Sep 2026 17:47:39 +0200 Subject: [PATCH 254/254] InputText: fixed a crash using Home key with WordWrap+ReadOnly. (#9561) Only the change in STB_TEXTEDIT_MOVELINESTART_IMPL() is required. The others are just for nicer semantic. --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1eee0b79b..30afc33be 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -71,6 +71,8 @@ Other Changes: - Tables: - Fixed a bug restoring correct .ini columns order when table use is Sortable but had no user changes. (regression from 1.92.9). (#9519, #9108) +- InpuText: + - Fixed a crash in some cases when using Home key in a WordWrap+ReadOnly field. (#9561) - Selectable: - Fixed `ImGuiSelectableFlags_SelectOnNav` triggering during NavInit. - Fonts: diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7737a41e6..ceabc13ba 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4232,7 +4232,7 @@ static int STB_TEXTEDIT_MOVELINESTART_IMPL(ImGuiInputTextState* obj, ImStb::STB_ const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks); if (p == p_cursor) // If we are already on a visible beginning-of-line, return real beginning-of-line (would be same as regular handler below) return (int)(p_bol - obj->TextSrc); - if (p_eol == p_cursor && obj->TextA[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left) + if (p_eol == p_cursor && obj->TextSrc[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left) return (int)(p_bol - obj->TextSrc); if (p_eol >= p_cursor) return (int)(p - obj->TextSrc); @@ -5057,7 +5057,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Select the buffer to render. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; - bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed; + bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextSrc : buf)[0] == 0) && !is_mixed; // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) @@ -5283,7 +5283,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { if (flags & ImGuiInputTextFlags_EscapeClearsAll) { - if (state->TextA.Data[0] != 0) + if (state->TextSrc[0] != 0) { revert_edit = true; } @@ -5376,14 +5376,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (flags & ImGuiInputTextFlags_EscapeClearsAll) { // Clear input - IM_ASSERT(state->TextA.Data[0] != 0); + IM_ASSERT(state->TextSrc[0] != 0); apply_new_text = ""; apply_new_text_length = 0; value_changed = true; char empty_string = 0; stb_textedit_replace(state, state->Stb, &empty_string, 0); } - else if (strcmp(state->TextA.Data, state->TextToRevertTo.Data) != 0) + else if (strcmp(state->TextSrc, state->TextToRevertTo.Data) != 0) { apply_new_text = state->TextToRevertTo.Data; apply_new_text_length = state->TextToRevertTo.Size - 1;