From 285e3042badff61de25aac44082dc757932e463c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 26 Nov 2025 14:49:11 +0100 Subject: [PATCH 01/28] Docs: tweaks. Add reference to imgui-module from main README. (#9092, #8868) --- docs/CHANGELOG.txt | 3 ++- docs/README.md | 4 +++- imgui.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 63ee6c4e0..87ce9e528 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -56,7 +56,8 @@ Other Changes: - Backends: - Vulkan: helper for creating a swapchain (used by examples and multi-viewports) selects `VkSwapchainCreateInfoKHR`'s `compositeAlpha` value based on - `cap.supportedCompositeAlpha`. (#8784) [@FelixStach] + `cap.supportedCompositeAlpha`, which seems to be required on some Android + devices. (#8784) [@FelixStach] ----------------------------------------------------------------------- diff --git a/docs/README.md b/docs/README.md index 45977e74f..8cc36713a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -39,10 +39,12 @@ Dear ImGui is particularly suited to integration in game engines (for tooling), ### Usage -**The core of Dear ImGui is self-contained within a few platform-agnostic files** which you can easily compile in your application/engine. They are all the files in the root folder of the repository (imgui*.cpp, imgui*.h). **No specific build process is required**. You can add the .cpp files into your existing project. +**The core of Dear ImGui is self-contained within a few platform-agnostic files** which you can easily compile in your application/engine. They are all the files in the root folder of the repository (`imgui*.cpp`, `imgui*.h`). **No specific build process is required**: you can add all files into your existing project. **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. + See the [Getting Started & Integration](#getting-started--integration) section of this document for more details. After Dear ImGui is set up in your application, you can use it from \_anywhere\_ in your program loop: diff --git a/imgui.cpp b/imgui.cpp index f878a116c..fd005d39c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4328,7 +4328,7 @@ void ImGui::Initialize() g.Viewports.push_back(viewport); g.TempBuffer.resize(1024 * 3 + 1, 0); - // Build KeysMayBeCharInput[] lookup table (1 bool per named key) + // Build KeysMayBeCharInput[] lookup table (1 bit per named key) for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9) || key == ImGuiKey_Tab || key == ImGuiKey_Space || key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period From fd887f5241eb43f68e8086c5abec83ec967a990e Mon Sep 17 00:00:00 2001 From: Leon Lysak Date: Wed, 26 Nov 2025 14:51:22 +0100 Subject: [PATCH 02/28] Backends: SDL_GPU3: use MSL on macOS and MetalLib on iOS to fix shader compatibility. (#9076) Edit original PR to keep metallib shader binary since next commit is going to use them. --- backends/imgui_impl_sdlgpu3.cpp | 15 +++++++++++ backends/imgui_impl_sdlgpu3_shaders.h | 34 +++++++++++++++++++++++++ backends/sdlgpu3/build_instructions.txt | 2 ++ docs/CHANGELOG.txt | 3 +++ examples/example_sdl3_sdlgpu3/main.cpp | 2 +- 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 8d8f0a4c4..197e9c411 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 +// 2025-11-26: Use MSL shaders on macOS to support macOS 10.14+ (instead of Metallib shaders requiring macOS 14+). Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-08-20: Added ImGui_ImplSDLGPU3_InitInfo::SwapchainComposition and ImGui_ImplSDLGPU3_InitInfo::PresentMode to configure how secondary viewports are created. // 2025-08-08: *BREAKING* Changed ImTextureID type from SDL_GPUTextureSamplerBinding* to SDL_GPUTexture*, which is more natural and easier for user to manage. If you need to change the current sampler, you can access the ImGui_ImplSDLGPU3_RenderState struct. (#8866, #8163, #7998, #7988) @@ -452,6 +453,19 @@ static void ImGui_ImplSDLGPU3_CreateShaders() #ifdef __APPLE__ else { +#include +#if TARGET_OS_OSX + // macOS: using MSL source + vertex_shader_info.entrypoint = "main0"; + vertex_shader_info.format = SDL_GPU_SHADERFORMAT_MSL; + vertex_shader_info.code = msl_vertex; + vertex_shader_info.code_size = sizeof(msl_vertex); + fragment_shader_info.entrypoint = "main0"; + fragment_shader_info.format = SDL_GPU_SHADERFORMAT_MSL; + fragment_shader_info.code = msl_fragment; + fragment_shader_info.code_size = sizeof(msl_fragment); +#elif TARGET_OS_IPHONE + // iOS device: using metallib blobs vertex_shader_info.entrypoint = "main0"; vertex_shader_info.format = SDL_GPU_SHADERFORMAT_METALLIB; vertex_shader_info.code = metallib_vertex; @@ -460,6 +474,7 @@ static void ImGui_ImplSDLGPU3_CreateShaders() fragment_shader_info.format = SDL_GPU_SHADERFORMAT_METALLIB; fragment_shader_info.code = metallib_fragment; fragment_shader_info.code_size = sizeof(metallib_fragment); +#endif } #endif bd->VertexShader = SDL_CreateGPUShader(v->Device, &vertex_shader_info); diff --git a/backends/imgui_impl_sdlgpu3_shaders.h b/backends/imgui_impl_sdlgpu3_shaders.h index f792aa6ab..ca94075f1 100644 --- a/backends/imgui_impl_sdlgpu3_shaders.h +++ b/backends/imgui_impl_sdlgpu3_shaders.h @@ -221,6 +221,40 @@ const uint8_t metallib_fragment[3787] = { 148,161,0,0,0,0,109,97,105,110,48,97,105,114,46,115,97,109,112,108,101,95,116,101,120,116,117,114,101,95,50,100,46,118,52,102,51,50,51,50,48,50,51,46,51,54,56,97,105,114,54,52,45,97, 112,112,108,101,45,109,97,99,111,115,120,49,52,46,48,46,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, }; +static uint8_t msl_vertex[800] = +{ + 35,105,110,99,108,117,100,101,32,60,109,101,116,97,108,95,115,116,100,108,105,98,62,10,35,105,110,99,108,117,100,101,32,60,115,105,109,100,47,115,105,109,100,46,104,62,10,10,117,115, + 105,110,103,32,110,97,109,101,115,112,97,99,101,32,109,101,116,97,108,59,10,10,115,116,114,117,99,116,32,95,57,10,123,10,32,32,32,32,102,108,111,97,116,52,32,67,111,108,111,114,59, + 10,32,32,32,32,102,108,111,97,116,50,32,85,86,59,10,125,59,10,10,115,116,114,117,99,116,32,85,66,79,10,123,10,32,32,32,32,102,108,111,97,116,50,32,117,83,99,97,108,101,59,10,32,32, + 32,32,102,108,111,97,116,50,32,117,84,114,97,110,115,108,97,116,101,59,10,125,59,10,10,115,116,114,117,99,116,32,109,97,105,110,48,95,111,117,116,10,123,10,32,32,32,32,102,108,111, + 97,116,52,32,79,117,116,95,67,111,108,111,114,32,91,91,117,115,101,114,40,108,111,99,110,48,41,93,93,59,10,32,32,32,32,102,108,111,97,116,50,32,79,117,116,95,85,86,32,91,91,117,115, + 101,114,40,108,111,99,110,49,41,93,93,59,10,32,32,32,32,102,108,111,97,116,52,32,103,108,95,80,111,115,105,116,105,111,110,32,91,91,112,111,115,105,116,105,111,110,93,93,59,10,125, + 59,10,10,115,116,114,117,99,116,32,109,97,105,110,48,95,105,110,10,123,10,32,32,32,32,102,108,111,97,116,50,32,97,80,111,115,32,91,91,97,116,116,114,105,98,117,116,101,40,48,41,93, + 93,59,10,32,32,32,32,102,108,111,97,116,50,32,97,85,86,32,91,91,97,116,116,114,105,98,117,116,101,40,49,41,93,93,59,10,32,32,32,32,102,108,111,97,116,52,32,97,67,111,108,111,114,32, + 91,91,97,116,116,114,105,98,117,116,101,40,50,41,93,93,59,10,125,59,10,10,118,101,114,116,101,120,32,109,97,105,110,48,95,111,117,116,32,109,97,105,110,48,40,109,97,105,110,48,95,105, + 110,32,105,110,32,91,91,115,116,97,103,101,95,105,110,93,93,44,32,99,111,110,115,116,97,110,116,32,85,66,79,38,32,117,98,111,32,91,91,98,117,102,102,101,114,40,48,41,93,93,41,10,123, + 10,32,32,32,32,109,97,105,110,48,95,111,117,116,32,111,117,116,32,61,32,123,125,59,10,32,32,32,32,95,57,32,79,117,116,32,61,32,123,125,59,10,32,32,32,32,79,117,116,46,67,111,108,111, + 114,32,61,32,105,110,46,97,67,111,108,111,114,59,10,32,32,32,32,79,117,116,46,85,86,32,61,32,105,110,46,97,85,86,59,10,32,32,32,32,111,117,116,46,103,108,95,80,111,115,105,116,105, + 111,110,32,61,32,102,108,111,97,116,52,40,40,105,110,46,97,80,111,115,32,42,32,117,98,111,46,117,83,99,97,108,101,41,32,43,32,117,98,111,46,117,84,114,97,110,115,108,97,116,101,44, + 32,48,46,48,44,32,49,46,48,41,59,10,32,32,32,32,111,117,116,46,103,108,95,80,111,115,105,116,105,111,110,46,121,32,42,61,32,40,45,49,46,48,41,59,10,32,32,32,32,111,117,116,46,79,117, + 116,95,67,111,108,111,114,32,61,32,79,117,116,46,67,111,108,111,114,59,10,32,32,32,32,111,117,116,46,79,117,116,95,85,86,32,61,32,79,117,116,46,85,86,59,10,32,32,32,32,114,101,116, + 117,114,110,32,111,117,116,59,10,125,10,10, +}; +static uint8_t msl_fragment[580] = +{ + 35,105,110,99,108,117,100,101,32,60,109,101,116,97,108,95,115,116,100,108,105,98,62,10,35,105,110,99,108,117,100,101,32,60,115,105,109,100,47,115,105,109,100,46,104,62,10,10,117,115, + 105,110,103,32,110,97,109,101,115,112,97,99,101,32,109,101,116,97,108,59,10,10,115,116,114,117,99,116,32,95,49,49,10,123,10,32,32,32,32,102,108,111,97,116,52,32,67,111,108,111,114, + 59,10,32,32,32,32,102,108,111,97,116,50,32,85,86,59,10,125,59,10,10,115,116,114,117,99,116,32,109,97,105,110,48,95,111,117,116,10,123,10,32,32,32,32,102,108,111,97,116,52,32,102,67, + 111,108,111,114,32,91,91,99,111,108,111,114,40,48,41,93,93,59,10,125,59,10,10,115,116,114,117,99,116,32,109,97,105,110,48,95,105,110,10,123,10,32,32,32,32,102,108,111,97,116,52,32, + 73,110,95,67,111,108,111,114,32,91,91,117,115,101,114,40,108,111,99,110,48,41,93,93,59,10,32,32,32,32,102,108,111,97,116,50,32,73,110,95,85,86,32,91,91,117,115,101,114,40,108,111,99, + 110,49,41,93,93,59,10,125,59,10,10,102,114,97,103,109,101,110,116,32,109,97,105,110,48,95,111,117,116,32,109,97,105,110,48,40,109,97,105,110,48,95,105,110,32,105,110,32,91,91,115,116, + 97,103,101,95,105,110,93,93,44,32,116,101,120,116,117,114,101,50,100,60,102,108,111,97,116,62,32,115,84,101,120,116,117,114,101,32,91,91,116,101,120,116,117,114,101,40,48,41,93,93, + 44,32,115,97,109,112,108,101,114,32,115,84,101,120,116,117,114,101,83,109,112,108,114,32,91,91,115,97,109,112,108,101,114,40,48,41,93,93,41,10,123,10,32,32,32,32,109,97,105,110,48, + 95,111,117,116,32,111,117,116,32,61,32,123,125,59,10,32,32,32,32,95,49,49,32,73,110,32,61,32,123,125,59,10,32,32,32,32,73,110,46,67,111,108,111,114,32,61,32,105,110,46,73,110,95,67, + 111,108,111,114,59,10,32,32,32,32,73,110,46,85,86,32,61,32,105,110,46,73,110,95,85,86,59,10,32,32,32,32,111,117,116,46,102,67,111,108,111,114,32,61,32,73,110,46,67,111,108,111,114, + 32,42,32,115,84,101,120,116,117,114,101,46,115,97,109,112,108,101,40,115,84,101,120,116,117,114,101,83,109,112,108,114,44,32,73,110,46,85,86,41,59,10,32,32,32,32,114,101,116,117,114, + 110,32,111,117,116,59,10,125,10,10, +}; #elif TARGET_OS_IPHONE const uint8_t metallib_vertex[3876] = { 77,84,76,66,1,0,2,0,7,0,0,130,18,0,1,0,36,15,0,0,0,0,0,0,88,0,0,0,0,0,0,0,123,0,0,0,0,0,0,0,219,0,0,0,0,0,0,0,49,0,0,0,0,0,0,0,12,1,0,0,0,0,0,0,8,0,0,0,0,0,0,0,20,1,0,0,0,0,0,0,16, diff --git a/backends/sdlgpu3/build_instructions.txt b/backends/sdlgpu3/build_instructions.txt index 25f4a5d28..79a09200e 100644 --- a/backends/sdlgpu3/build_instructions.txt +++ b/backends/sdlgpu3/build_instructions.txt @@ -34,6 +34,8 @@ Instructions to rebuild imgui_impl_sdlgpu3_shaders.h xcrun -sdk macosx metallib -o vertex.metallib -c vertex.ir xcrun -sdk macosx metallib -o fragment.metallib -c fragment.ir + note: use .metal outputs for updating msl_vertex / msl_fragment variables, and use .metallib outputs for metallib_vertex / metallib_fragment variables + Proceed to step 4 diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 87ce9e528..488eba81d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -54,6 +54,9 @@ Other Changes: - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Backends: + - SDL_GPU3: on macOS, use MSL shaders in order to support macOS 10.14+ + (instead of Metallib shaders requiring macOS 14+). (#9076) [@Niminem] + Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. - Vulkan: helper for creating a swapchain (used by examples and multi-viewports) selects `VkSwapchainCreateInfoKHR`'s `compositeAlpha` value based on `cap.supportedCompositeAlpha`, which seems to be required on some Android diff --git a/examples/example_sdl3_sdlgpu3/main.cpp b/examples/example_sdl3_sdlgpu3/main.cpp index 84b0a254f..a33c0cfe9 100644 --- a/examples/example_sdl3_sdlgpu3/main.cpp +++ b/examples/example_sdl3_sdlgpu3/main.cpp @@ -47,7 +47,7 @@ int main(int, char**) SDL_ShowWindow(window); // Create GPU Device - SDL_GPUDevice* gpu_device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_METALLIB,true,nullptr); + SDL_GPUDevice* gpu_device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB, true, nullptr); if (gpu_device == nullptr) { printf("Error: SDL_CreateGPUDevice(): %s\n", SDL_GetError()); From 75db81cf0882992b7c750ee76afaa0d7ed57877c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 26 Nov 2025 15:05:05 +0100 Subject: [PATCH 03/28] Backends: SDL_GPU3: select between metallib and MSL shaders based on availability. (#9076) Amend fd887f5 --- backends/imgui_impl_sdlgpu3.cpp | 49 +++++++++++++++++---------------- docs/CHANGELOG.txt | 6 ++-- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 197e9c411..e5934c302 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -22,7 +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 -// 2025-11-26: Use MSL shaders on macOS to support macOS 10.14+ (instead of Metallib shaders requiring macOS 14+). Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. +// 2025-11-26: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-08-20: Added ImGui_ImplSDLGPU3_InitInfo::SwapchainComposition and ImGui_ImplSDLGPU3_InitInfo::PresentMode to configure how secondary viewports are created. // 2025-08-08: *BREAKING* Changed ImTextureID type from SDL_GPUTextureSamplerBinding* to SDL_GPUTexture*, which is more natural and easier for user to manage. If you need to change the current sampler, you can access the ImGui_ImplSDLGPU3_RenderState struct. (#8866, #8163, #7998, #7988) @@ -453,28 +453,31 @@ static void ImGui_ImplSDLGPU3_CreateShaders() #ifdef __APPLE__ else { -#include -#if TARGET_OS_OSX - // macOS: using MSL source - vertex_shader_info.entrypoint = "main0"; - vertex_shader_info.format = SDL_GPU_SHADERFORMAT_MSL; - vertex_shader_info.code = msl_vertex; - vertex_shader_info.code_size = sizeof(msl_vertex); - fragment_shader_info.entrypoint = "main0"; - fragment_shader_info.format = SDL_GPU_SHADERFORMAT_MSL; - fragment_shader_info.code = msl_fragment; - fragment_shader_info.code_size = sizeof(msl_fragment); -#elif TARGET_OS_IPHONE - // iOS device: using metallib blobs - vertex_shader_info.entrypoint = "main0"; - vertex_shader_info.format = SDL_GPU_SHADERFORMAT_METALLIB; - vertex_shader_info.code = metallib_vertex; - vertex_shader_info.code_size = sizeof(metallib_vertex); - fragment_shader_info.entrypoint = "main0"; - fragment_shader_info.format = SDL_GPU_SHADERFORMAT_METALLIB; - fragment_shader_info.code = metallib_fragment; - fragment_shader_info.code_size = sizeof(metallib_fragment); -#endif + SDL_GPUShaderFormat supported_formats = SDL_GetGPUShaderFormats(v->Device); + if (supported_formats & SDL_GPU_SHADERFORMAT_METALLIB) + { + // Using metallib blobs (macOS 14+, iOS) + vertex_shader_info.entrypoint = "main0"; + vertex_shader_info.format = SDL_GPU_SHADERFORMAT_METALLIB; + vertex_shader_info.code = metallib_vertex; + vertex_shader_info.code_size = sizeof(metallib_vertex); + fragment_shader_info.entrypoint = "main0"; + fragment_shader_info.format = SDL_GPU_SHADERFORMAT_METALLIB; + fragment_shader_info.code = metallib_fragment; + fragment_shader_info.code_size = sizeof(metallib_fragment); + } + else if (supported_formats & SDL_GPU_SHADERFORMAT_MSL) + { + // macOS: using MSL source + vertex_shader_info.entrypoint = "main0"; + vertex_shader_info.format = SDL_GPU_SHADERFORMAT_MSL; + vertex_shader_info.code = msl_vertex; + vertex_shader_info.code_size = sizeof(msl_vertex); + fragment_shader_info.entrypoint = "main0"; + fragment_shader_info.format = SDL_GPU_SHADERFORMAT_MSL; + fragment_shader_info.code = msl_fragment; + fragment_shader_info.code_size = sizeof(msl_fragment); + } } #endif bd->VertexShader = SDL_CreateGPUShader(v->Device, &vertex_shader_info); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 488eba81d..ee3186bd9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -54,9 +54,9 @@ Other Changes: - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Backends: - - SDL_GPU3: on macOS, use MSL shaders in order to support macOS 10.14+ - (instead of Metallib shaders requiring macOS 14+). (#9076) [@Niminem] - Requires calling SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. + - SDL_GPU3: macOS version can use MSL shaders in order to support macOS 10.14+ + (vs Metallib shaders requiring macOS 14+). Requires application calling + SDL_CreateGPUDevice() with SDL_GPU_SHADERFORMAT_MSL. (#9076) [@Niminem] - Vulkan: helper for creating a swapchain (used by examples and multi-viewports) selects `VkSwapchainCreateInfoKHR`'s `compositeAlpha` value based on `cap.supportedCompositeAlpha`, which seems to be required on some Android From f0699effedb45108a0de18b9e8810e5f84597d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Tassoux?= Date: Sat, 22 Nov 2025 12:47:10 +0100 Subject: [PATCH 04/28] Examples: Win32+DirectX12: Disable breaking on the D3D12_MESSAGE_ID_FENCE_ZERO_WAIT warning. (#9093, #9084) --- examples/example_win32_directx12/main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 5a8dc0381..5430fde26 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -351,6 +351,15 @@ bool CreateDeviceD3D(HWND hWnd) pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); + + // Disable breaking on this warning because of a suspected bug in the D3D12 SDK layer, see #9084 for details. + const int D3D12_MESSAGE_ID_FENCE_ZERO_WAIT_ = 1424; // not in all copies of d3d12sdklayers.h + D3D12_MESSAGE_ID disabledMessages[] = { (D3D12_MESSAGE_ID)D3D12_MESSAGE_ID_FENCE_ZERO_WAIT_ }; + D3D12_INFO_QUEUE_FILTER filter = {}; + filter.DenyList.NumIDs = 1; + filter.DenyList.pIDList = disabledMessages; + pInfoQueue->AddStorageFilterEntries(&filter); + pInfoQueue->Release(); pdx12Debug->Release(); } From c36162fc6c86ca8b53b06d1c7229ec1b161d22a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 26 Nov 2025 17:29:37 +0100 Subject: [PATCH 05/28] Internals: add SetContextName() helper. (#9097) Amend 37c243b. --- imgui.cpp | 7 +++++++ imgui_internal.h | 11 ++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index fd005d39c..6ca0d0148 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4443,6 +4443,13 @@ void ImGui::Shutdown() g.Initialized = false; } +// When using multiple context it can be helpful to give name a name. +// (A) Will be visible in debugger, (B) Will be included in all IMGUI_DEBUG_LOG() calls, (C) Should be <= 15 characters long. +void ImGui::SetContextName(ImGuiContext* ctx, const char* name) +{ + ImStrncpy(ctx->ContextName, name, IM_ARRAYSIZE(ctx->ContextName)); +} + // No specific ordering/dependency support, will see as needed ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) { diff --git a/imgui_internal.h b/imgui_internal.h index 557a380c7..af90db9df 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3193,6 +3193,12 @@ namespace ImGui IMGUI_API void Initialize(); IMGUI_API void Shutdown(); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + // Context name & generic context hooks + IMGUI_API void SetContextName(ImGuiContext* ctx, const char* name); + IMGUI_API ImGuiID AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook); + IMGUI_API void RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_to_remove); + IMGUI_API void CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType type); + // NewFrame IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs); IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos); @@ -3202,11 +3208,6 @@ namespace ImGui IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); - // Generic context hooks - IMGUI_API ImGuiID AddContextHook(ImGuiContext* context, const ImGuiContextHook* hook); - IMGUI_API void RemoveContextHook(ImGuiContext* context, ImGuiID hook_to_remove); - IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); - // Viewports IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); IMGUI_API void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport); From 9c75ef5a6158befe6c0611dab6f50c8ff70a228a Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 26 Nov 2025 18:25:30 +0100 Subject: [PATCH 06/28] Tables: clarify TableNextRow() row_height and adjust demo to make this clearer (demo height were arbitrary and therefore misleading). --- docs/CHANGELOG.txt | 3 +++ imgui.h | 2 +- imgui_demo.cpp | 7 ++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ee3186bd9..9cc1edb6f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -61,6 +61,9 @@ Other Changes: selects `VkSwapchainCreateInfoKHR`'s `compositeAlpha` value based on `cap.supportedCompositeAlpha`, which seems to be required on some Android devices. (#8784) [@FelixStach] +- Examples: + - Win32+DirectX12: ignore seemingly incorrect D3D12_MESSAGE_ID_FENCE_ZERO_WAIT + warning on startups on some setups. (#9084, #9093) [@RT2Code, @LeoGautheron] ----------------------------------------------------------------------- diff --git a/imgui.h b/imgui.h index fe1385da2..ac64c2ded 100644 --- a/imgui.h +++ b/imgui.h @@ -897,7 +897,7 @@ namespace ImGui // - 5. Call EndTable() IMGUI_API bool BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! - IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. + IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. 'min_row_height' include the minimum top and bottom padding aka CellPadding.y * 2.0f. IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 35451730c..1e49dd6fa 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -6509,7 +6509,7 @@ static void DemoWindowTables() ImGui::TableNextColumn(); ImGui::Text("A0 Row 0"); { - float rows_height = TEXT_BASE_HEIGHT * 2; + float rows_height = (TEXT_BASE_HEIGHT * 2.0f) + (ImGui::GetStyle().CellPadding.y * 2.0f); if (ImGui::BeginTable("table_nested2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { ImGui::TableSetupColumn("B0"); @@ -6551,7 +6551,7 @@ static void DemoWindowTables() { for (int row = 0; row < 8; row++) { - float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row); + float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row + ImGui::GetStyle().CellPadding.y * 2.0f); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); ImGui::TableNextColumn(); ImGui::Text("min_row_height = %.2f", min_row_height); @@ -6655,9 +6655,10 @@ static void DemoWindowTables() ImGui::SameLine(); if (ImGui::BeginTable("table3", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f))) { + const float rows_height = TEXT_BASE_HEIGHT * 1.5f + ImGui::GetStyle().CellPadding.y * 2.0f; for (int row = 0; row < 3; row++) { - ImGui::TableNextRow(0, TEXT_BASE_HEIGHT * 1.5f); + ImGui::TableNextRow(0, rows_height); for (int column = 0; column < 3; column++) { ImGui::TableNextColumn(); From ae873b1e0d2fedd971973aa6e00e2901ea92b1d0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 12:33:49 +0100 Subject: [PATCH 07/28] Misc: rename extraneous parenthesizes from return statements. --- examples/example_apple_opengl2/main.mm | 4 ++-- imgui.cpp | 16 ++++++++-------- imgui_demo.cpp | 6 +++--- imgui_internal.h | 2 +- imgui_tables.cpp | 5 +++++ imgui_widgets.cpp | 2 +- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index d7aa7eb5b..14f3f4321 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -169,7 +169,7 @@ -(NSWindow*)window { if (_window != nil) - return (_window); + return _window; NSRect viewRect = NSMakeRect(100.0, 100.0, 100.0 + 1280.0, 100 + 800.0); @@ -179,7 +179,7 @@ [_window setOpaque:YES]; [_window makeKeyAndOrderFront:NSApp]; - return (_window); + return _window; } -(void)setupMenu diff --git a/imgui.cpp b/imgui.cpp index 6ca0d0148..640d6b34a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2029,7 +2029,7 @@ bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; - return ((b1 == b2) && (b2 == b3)); + return (b1 == b2) && (b2 == b3); } void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) @@ -3129,7 +3129,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE static bool GetSkipItemForListClipping() { ImGuiContext& g = *GImGui; - return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); + return g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems; } static void ImGuiListClipper_SortAndFuseRanges(ImVector& ranges, int offset = 0) @@ -5290,7 +5290,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() static bool IsWindowActiveAndVisible(ImGuiWindow* window) { - return (window->Active) && (!window->Hidden); + return window->Active && !window->Hidden; } // The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) @@ -5692,7 +5692,7 @@ static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) return d; if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) return d; - return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); + return a->BeginOrderWithinParent - b->BeginOrderWithinParent; } static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) @@ -6168,7 +6168,7 @@ bool ImGui::IsItemDeactivated() ImGuiContext& g = *GImGui; if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount); + return g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount; } bool ImGui::IsItemDeactivatedAfterEdit() @@ -9394,7 +9394,7 @@ int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, flo if (t0 >= t1) return 0; if (repeat_rate <= 0.0f) - return (t0 < repeat_delay) && (t1 >= repeat_delay); + return t0 < repeat_delay && t1 >= repeat_delay; const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate); const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate); const int count = count_t1 - count_t0; @@ -10513,7 +10513,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); if (owner_id == ImGuiKeyOwner_Any) - return (owner_data->LockThisFrame == false); + return owner_data->LockThisFrame == false; // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of ActiveId/HoveredId // are acting as filter before this has a chance to filter), but sane as soon as user tries to look into things. @@ -12693,7 +12693,7 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) if (flags & ImGuiFocusedFlags_ChildWindows) return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); else - return (ref_window == cur_window); + return ref_window == cur_window; } static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 1e49dd6fa..8c4a2a426 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2530,7 +2530,7 @@ struct ExampleDualListBox { const int* a = (const int*)lhs; const int* b = (const int*)rhs; - return (*a - *b); + return *a - *b; } void SortItems(int n) { @@ -5545,7 +5545,7 @@ struct MyItem // qsort() is instable so always return a way to differentiate items. // Your own compare function may want to avoid fallback on implicit sort specs. // e.g. a Name compare if it wasn't already part of the sort specs. - return (a->ID - b->ID); + return a->ID - b->ID; } }; const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; @@ -10516,7 +10516,7 @@ struct ExampleAsset if (delta < 0) return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; } - return ((int)a->ID - (int)b->ID); + return (int)a->ID - (int)b->ID; } }; const ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL; diff --git a/imgui_internal.h b/imgui_internal.h index af90db9df..788d1d5e6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3651,7 +3651,7 @@ namespace ImGui IMGUI_API void InputTextDeactivateHook(ImGuiID id); IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags); 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.ActiveId == id && g.TempInputId == 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_tables.cpp b/imgui_tables.cpp index 25d945d2a..0a259e0c9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2458,6 +2458,11 @@ void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) // - TableDrawBorders() [Internal] //------------------------------------------------------------------------- + +// FIXME: This could be abstracted and merged with PushColumnsBackground(), by creating a generic struct +// with storage for backup cliprect + backup channel + storage for splitter pointer, new clip rect. +// This would slightly simplify caller code. + // Bg2 is used by Selectable (and possibly other widgets) to render to the background. // Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect. void ImGui::TablePushBackgroundChannel() diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 9009bbc8c..1a13eea3d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1834,7 +1834,7 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs; if (int d = (int)(b->Width - a->Width)) return d; - return (b->Index - a->Index); + return b->Index - a->Index; } // Shrink excess width from a set of item, by removing width from the larger items first. From 1f5466579938ac0a6c7b4df3fefa6761421513a5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 19:46:13 +0100 Subject: [PATCH 08/28] Menus: fixed MenuItem() label baseline when using inside a line with an offset. Amend f8fae022704 --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9cc1edb6f..f317f620b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -51,6 +51,7 @@ Other Changes: - Textures: - Fixed a building issue when ImTextureID is defined as a struct. - Fixed displaying texture # in Metrics/Debugger window. +- Menus: fixed MenuItem() label baseline when using inside a line with an offset. - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Backends: diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1a13eea3d..d917d30a1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9434,21 +9434,22 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); float min_w = window->DC.MenuColumns.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); pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) { - RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label); if (icon_w > 0.0f) - RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); if (shortcut_w > 0.0f) { PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); LogSetNextTextDecoration("(", ")"); - RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); + RenderText(text_pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } if (selected) - RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); + 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); } } IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); From 3ff8c466bf9e02f8e09ec16ec978d6bf638304e1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 20:05:07 +0100 Subject: [PATCH 09/28] Menus: fixed BeginMenuEx() icon/arrow baseline when using inside a line with an offset. Amend 1f54665. --- imgui_widgets.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d917d30a1..8c5614cae 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9227,13 +9227,13 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); - ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); LogSetNextTextDecoration("", ">"); - RenderText(text_pos, label); + RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label); if (icon_w > 0.0f) - RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); - RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); + RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon); + RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); } if (!enabled) EndDisabled(); From 2026e3db882332bc5de5ea232d2808331131580b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 21:48:10 +0100 Subject: [PATCH 10/28] Menus: fixed BeginMenu() child popup position when used inside a line with a baseline offset. --- docs/CHANGELOG.txt | 4 +++- imgui_widgets.cpp | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f317f620b..a8fdd4704 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -51,7 +51,9 @@ Other Changes: - Textures: - Fixed a building issue when ImTextureID is defined as a struct. - Fixed displaying texture # in Metrics/Debugger window. -- Menus: fixed MenuItem() label baseline when using inside a line with an offset. +- Menus: + - Fixed MenuItem() label position and BeginMenu() arrow/icon/popup positions, + when used inside a line with a baseline offset. - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Backends: diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8c5614cae..ebb7491ff 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -9192,7 +9192,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // 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(). // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. - ImVec2 popup_pos, pos = window->DC.CursorPos; + ImVec2 popup_pos; + ImVec2 pos = window->DC.CursorPos; PushID(label); if (!enabled) BeginDisabled(); @@ -9206,34 +9207,34 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Menu inside a horizontal menu bar // Selectable extend their highlight by half ItemSpacing in each direction. // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() - popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); float w = label_size.x; - ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); LogSetNextTextDecoration("[", "]"); RenderText(text_pos, label); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight); } else { // Menu inside a regular/vertical menu // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.) - popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); - ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); LogSetNextTextDecoration("", ">"); RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label); if (icon_w > 0.0f) RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon); 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); } if (!enabled) EndDisabled(); From 1a62292ac0b60308dd6c90084524e4a0ed5ac99f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 22:37:00 +0100 Subject: [PATCH 11/28] Tables: fixed an issue where a very thin scrolling table would advance parent layout slightly differently depending on its visibility. --- docs/CHANGELOG.txt | 4 ++++ imgui.cpp | 8 ++++---- imgui_internal.h | 2 ++ imgui_tables.cpp | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a8fdd4704..838f8e1c3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -54,6 +54,10 @@ Other Changes: - Menus: - Fixed MenuItem() label position and BeginMenu() arrow/icon/popup positions, when used inside a line with a baseline offset. +- Tables: + - Fixed an issue where a very thin scrolling table would advance parent layout + slightly differently depending on its visibility (caused by a mismatch + between hard minimum window size and table minimum size). - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Backends: diff --git a/imgui.cpp b/imgui.cpp index 640d6b34a..536bb17b0 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6584,13 +6584,13 @@ static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) ImVec2 size_min; if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) { - size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : 4.0f; - size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : 4.0f; + size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : IMGUI_WINDOW_HARD_MIN_SIZE; + size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : IMGUI_WINDOW_HARD_MIN_SIZE; } else { - size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : 4.0f; - size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : 4.0f; + size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : IMGUI_WINDOW_HARD_MIN_SIZE; + size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : IMGUI_WINDOW_HARD_MIN_SIZE; } // Reduce artifacts with very small windows diff --git a/imgui_internal.h b/imgui_internal.h index 788d1d5e6..318d013d4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2561,6 +2561,8 @@ struct ImGuiContext // [SECTION] ImGuiWindowTempData, ImGuiWindow //----------------------------------------------------------------------------- +#define IMGUI_WINDOW_HARD_MIN_SIZE 4.0f + // Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. // (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..) // (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 0a259e0c9..0e8c5f17e 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -335,7 +335,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[]. const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; const ImVec2 avail_size = GetContentRegionAvail(); - const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f)); + const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window ? ImMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) : 0.0f)); const ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); const bool outer_window_is_measuring_size = (outer_window->AutoFitFramesX > 0) || (outer_window->AutoFitFramesY > 0); // Doesn't apply to AlwaysAutoResize windows! if (use_child_window && IsClippedEx(outer_rect, 0) && !outer_window_is_measuring_size) From 7c6febed6620a6d356080ddb229130f97ab07c90 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 22:54:51 +0100 Subject: [PATCH 12/28] Tables: fixed an issue where submitting non-integer row heights would eventually advance table parent layout by +0/+1 depending on its visibility. --- 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 838f8e1c3..044c099ae 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -58,6 +58,8 @@ Other Changes: - Fixed an issue where a very thin scrolling table would advance parent layout slightly differently depending on its visibility (caused by a mismatch between hard minimum window size and table minimum size). + - Fixed an issue where submitting non-integer row heights would eventually + advance table parent layout by +0/+1 depending on its visibility. - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Backends: diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 0e8c5f17e..4aa868317 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1383,7 +1383,7 @@ void ImGui::EndTable() inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize; inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize; inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos; - const float inner_content_max_y = table->RowPosY2; + const float inner_content_max_y = ImCeil(table->RowPosY2); // Rounding final position is important as we currently don't round row height ('Demo->Tables->Outer Size' demo uses non-integer heights) IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y); if (inner_window != outer_window) inner_window->DC.CursorMaxPos.y = inner_content_max_y; From 47766ca40335db5cdc62e14d17a7c8a139eb7ea8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 23:40:52 +0100 Subject: [PATCH 13/28] Demo: fix in 'Demo->Selection->Multi-Select in a Table' section. --- imgui_demo.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 8c4a2a426..f3bf332d6 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2863,7 +2863,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d const int ITEMS_COUNT = 10000; ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); - if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter)) + if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter, ImVec2(0.0f, ImGui::GetFontSize() * 20))) { ImGui::TableSetupColumn("Object"); ImGui::TableSetupColumn("Action"); @@ -2884,6 +2884,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d { ImGui::TableNextRow(); ImGui::TableNextColumn(); + ImGui::PushID(n); char label[64]; sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); bool item_is_selected = selection.Contains((ImGuiID)n); @@ -2891,6 +2892,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap); ImGui::TableNextColumn(); ImGui::SmallButton("hello"); + ImGui::PopID(); } } From 620a33dd85ddd405a76cd5bd4e3995e33585127d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Nov 2025 22:01:25 +0100 Subject: [PATCH 14/28] TreeNode: fixed highlight position when used inside a line with a large text baseline offset. Most old logic e.g. df749e3f135, ec0e953ccac. Never quite worked for this situation. --- docs/CHANGELOG.txt | 4 ++++ imgui.h | 2 +- imgui_demo.cpp | 21 +++++++++++++++------ imgui_widgets.cpp | 15 ++++++++------- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 044c099ae..590fa118c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -54,6 +54,10 @@ Other Changes: - Menus: - Fixed MenuItem() label position and BeginMenu() arrow/icon/popup positions, when used inside a line with a baseline offset. +- TreeNode: + - Fixed highlight position when used inside a line with a large text baseline offset. + (never quite worked in this situation; but then most of the time the text + baseline offset ends up being zero or FramePadding.y for a given line). - Tables: - Fixed an issue where a very thin scrolling table would advance parent layout slightly differently depending on its visibility (caused by a mismatch diff --git a/imgui.h b/imgui.h index ac64c2ded..facb99975 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.6 WIP" -#define IMGUI_VERSION_NUM 19251 +#define IMGUI_VERSION_NUM 19252 #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 f3bf332d6..1d914b371 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4086,6 +4086,7 @@ static void DemoWindowWidgetsTreeNodes() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_FramePadding", &base_flags, ImGuiTreeNodeFlags_FramePadding); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsToParent", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsToParent); HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags"); @@ -4750,7 +4751,7 @@ static void DemoWindowLayout() // Tree // (here the node appears after a button and has odd intent, so we use ImGuiTreeNodeFlags_DrawLinesNone to disable hierarchy outline) const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::Button("Button##1"); + ImGui::Button("Button##1"); // Will make line higher ImGui::SameLine(0.0f, spacing); if (ImGui::TreeNodeEx("Node##1", ImGuiTreeNodeFlags_DrawLinesNone)) { @@ -4760,14 +4761,22 @@ static void DemoWindowLayout() ImGui::TreePop(); } + const float padding = (float)(int)(ImGui::GetFontSize() * 1.20f); // Large padding + ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, padding); + ImGui::Button("Button##2"); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNodeEx("Node##2", ImGuiTreeNodeFlags_DrawLinesNone)) + ImGui::TreePop(); + // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. // Otherwise you can use SmallButton() (smaller fit). ImGui::AlignTextToFramePadding(); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add - // other contents below the node. - bool node_open = ImGui::TreeNode("Node##2"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); + // other contents "inside" the node. + bool node_open = ImGui::TreeNode("Node##3"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##3"); if (node_open) { // Placeholder tree data @@ -4777,13 +4786,13 @@ static void DemoWindowLayout() } // Bullet - ImGui::Button("Button##3"); + ImGui::Button("Button##4"); ImGui::SameLine(0.0f, spacing); ImGui::BulletText("Bullet text"); ImGui::AlignTextToFramePadding(); ImGui::BulletText("Node"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); + ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##5"); ImGui::Unindent(); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ebb7491ff..2e05af1c6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6776,7 +6776,6 @@ static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } -// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); @@ -6785,26 +6784,28 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; + + // When not framed, we vertically increase height up to typical framed widget height const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; - const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); + const bool use_frame_padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)); + const ImVec2 padding = use_frame_padding ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); if (!label_end) label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing - const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it + const float text_offset_y = use_frame_padding ? ImMax(style.FramePadding.y, window->DC.CurrLineTextBaseOffset) : window->DC.CurrLineTextBaseOffset; // Latch before ItemSize changes it const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow - // We vertically grow up to current line height up the typical widget height. - const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); + const float frame_height = label_size.y + padding.y * 2; const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL); ImRect frame_bb; frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; - frame_bb.Min.y = window->DC.CursorPos.y; + frame_bb.Min.y = window->DC.CursorPos.y + (text_offset_y - padding.y); frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; - frame_bb.Max.y = window->DC.CursorPos.y + frame_height; + frame_bb.Max.y = window->DC.CursorPos.y + (text_offset_y - padding.y) + frame_height; if (display_frame) { const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits From bfe137893abd0d08fa6617ae247d5a3379bdd571 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 1 Dec 2025 17:02:51 +0100 Subject: [PATCH 15/28] Debug Log: fixed incorrectly printing characters in IO log when submitting non-ASCII values to io.AddInputCharacter(). (#9099) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 590fa118c..058c853d3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -66,6 +66,9 @@ Other Changes: advance table parent layout by +0/+1 depending on its visibility. - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] +- Debug Tools: + - Debug Log: fixed incorrectly printing characters in IO log when submitting + non-ASCII values to io.AddInputCharacter(). (#9099) - Backends: - SDL_GPU3: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires application calling diff --git a/imgui.cpp b/imgui.cpp index 536bb17b0..e21353a62 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10332,11 +10332,12 @@ static const char* GetMouseSourceName(ImGuiMouseSource source) static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) { ImGuiContext& g = *GImGui; + char buf[5]; if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (%.1f, %.1f) (%s)\n", prefix, e->MousePos.PosX, e->MousePos.PosY, GetMouseSourceName(e->MousePos.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseButton %d %s (%s)\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up", GetMouseSourceName(e->MouseButton.MouseSource)); return; } if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseWheel (%.3f, %.3f) (%s)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY, GetMouseSourceName(e->MouseWheel.MouseSource)); return; } if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("[io] %s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; } - if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("[io] %s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; } + if (e->Type == ImGuiInputEventType_Text) { ImTextCharToUtf8(buf, e->Text.Char); IMGUI_DEBUG_LOG_IO("[io] %s: Text: '%s' (U+%08X)\n", prefix, buf, e->Text.Char); return; } if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("[io] %s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; } } #endif From 962cc2381d44ab3699c281880e0b1d30dfe02bb6 Mon Sep 17 00:00:00 2001 From: ulhc <350246356@qq.com> Date: Fri, 28 Nov 2025 22:45:42 +0800 Subject: [PATCH 16/28] Backends: Win32: viewports created by backend direct messages to DefWindowProcW() in order to support Unicode text inputs. (#9099, #3653, #5961) --- backends/imgui_impl_win32.cpp | 4 ++-- docs/CHANGELOG.txt | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index 37b4d0fbe..44f956885 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -1372,7 +1372,7 @@ static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd, // Allow secondary viewport WndProc to be called regardless of current context ImGuiContext* ctx = (ImGuiContext*)::GetPropA(hWnd, "IMGUI_CONTEXT"); if (ctx == NULL) - return DefWindowProc(hWnd, msg, wParam, lParam); // unlike ImGui_ImplWin32_WndProcHandler() we are called directly by Windows, we can't just return 0. + return ::DefWindowProcW(hWnd, msg, wParam, lParam); // unlike ImGui_ImplWin32_WndProcHandler() we are called directly by Windows, we can't just return 0. ImGuiIO& io = ImGui::GetIO(ctx); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(ctx); @@ -1407,7 +1407,7 @@ static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd, } } if (result == 0) - result = DefWindowProc(hWnd, msg, wParam, lParam); + result = ::DefWindowProcW(hWnd, msg, wParam, lParam); return result; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index dfa0a9280..c1129353e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -58,6 +58,12 @@ Other Changes: selects `VkSwapchainCreateInfoKHR`'s `compositeAlpha` value based on `cap.supportedCompositeAlpha`. (#8784) [@FelixStach] +Docking+Viewports Branch: + +- Backends: + - Win32: viewports created by backend forcefully direct messages to + DefWindowProcW() in order to support Unicode text input. (#9099, #3653, #5961) [@ulhc] + ----------------------------------------------------------------------- VERSION 1.92.5 (Released 2025-11-20) From 87b193399e3fc7e1ab3902ed027d9b5dc9c9b4d5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Dec 2025 13:30:46 +0100 Subject: [PATCH 17/28] Backends: Win32: change param to MultiByteToWideChar() to be consistent (should be no-op?). Amend 0a7054c7e4 (#5725, #1807, #471, #2815, #1060 + #9099, #3653, #5961) --- backends/imgui_impl_win32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index dc7e66f02..b1d715436 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -784,7 +784,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA else { wchar_t wch = 0; - ::MultiByteToWideChar(bd->KeyboardCodePage, MB_PRECOMPOSED, (char*)&wParam, 1, &wch, 1); + ::MultiByteToWideChar(bd->KeyboardCodePage, MB_PRECOMPOSED, (char*)&wParam, 2, &wch, 1); io.AddInputCharacter(wch); } return 0; From d27dce58cda087f72418f891c7f95d9ce596fd6d Mon Sep 17 00:00:00 2001 From: ulhc <350246356@qq.com> Date: Fri, 28 Nov 2025 23:05:45 +0800 Subject: [PATCH 18/28] Backends: Win32: handle WM_IME_CHAR/WM_IME_COMPOSITION messages to support Unicode inputs on MBCS Windows. (#9099, #3653, #5961) --- backends/imgui_impl_win32.cpp | 19 +++++++++++++++++++ docs/CHANGELOG.txt | 2 ++ 2 files changed, 21 insertions(+) diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index b1d715436..5f42466ae 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) +// 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. // 2025-09-23: Inputs: Minor optimization not submitting gamepad input if packet number has not changed. // 2025-09-18: Call platform_io.ClearPlatformHandlers() on shutdown. @@ -788,6 +789,24 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA io.AddInputCharacter(wch); } return 0; + case WM_IME_COMPOSITION: + { + // Handling WM_IME_COMPOSITION ensure that WM_IME_CHAR value is correct even for MBCS apps. + // (see #9099, #3653 and https://stackoverflow.com/questions/77450354 topics) + LRESULT result = ::DefWindowProcW(hwnd, msg, wParam, lParam); + return (lParam & GCS_RESULTSTR) ? 1 : result; + } + case WM_IME_CHAR: + if (::IsWindowUnicode(hwnd) == FALSE) + { + if (::IsDBCSLeadByte(HIBYTE(wParam))) + wParam = (WPARAM)MAKEWORD(HIBYTE(wParam), LOBYTE(wParam)); + wchar_t wch = 0; + ::MultiByteToWideChar(bd->KeyboardCodePage, MB_PRECOMPOSED, (char*)&wParam, 2, &wch, 1); + io.AddInputCharacterUTF16(wch); + return 1; + } + return 0; case WM_SETCURSOR: // This is required to restore cursor when transitioning from e.g resize borders to client area. if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor(io, bd->LastMouseCursor)) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 058c853d3..14ecb0dd0 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -77,6 +77,8 @@ Other Changes: selects `VkSwapchainCreateInfoKHR`'s `compositeAlpha` value based on `cap.supportedCompositeAlpha`, which seems to be required on some Android devices. (#8784) [@FelixStach] + - Win32: handle WM_IME_CHAR/WM_IME_COMPOSITION to support Unicode inputs on + MBCS (non-Unicode) Windows. (#9099, #3653, #5961) [@ulhc, @ocornut, @Othereum] - Examples: - Win32+DirectX12: ignore seemingly incorrect D3D12_MESSAGE_ID_FENCE_ZERO_WAIT warning on startups on some setups. (#9084, #9093) [@RT2Code, @LeoGautheron] From 6e0ee6ff03324b58b20001f25d18d40dfb921a91 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Dec 2025 20:15:25 +0100 Subject: [PATCH 19/28] Fonts: removed misleading SizePixels >= 0.0f test and stbtt_ScaleForMappingEmToPixels() call in ImGui_ImplStbTrueType_FontSrcInit(). (#8857) Logic was picked in 9a9712807e while extracting code from stbtt_PackFontRangesGatherRects(), but ScaleForMappingEmToPixels() was actually never called: we assert against negative SizePixels since 2015. --- imgui_draw.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 88ad02966..b01e57358 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4596,10 +4596,7 @@ static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* if (src->MergeMode && src->SizePixels == 0.0f) src->SizePixels = ref_size; - if (src->SizePixels >= 0.0f) - bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); - else - bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); if (src->MergeMode && src->SizePixels != 0.0f && ref_size != 0.0f) bd_font_data->ScaleFactor *= src->SizePixels / ref_size; // FIXME-NEWATLAS: Should tidy up that a bit From d1e262ad555e0410a792cae973b35baf8933e88b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Dec 2025 15:57:31 +0100 Subject: [PATCH 20/28] Internals: refactor RenderRectFilledRangeH() into RenderRectFilledInRangeH() to take absolute coordinates instead of normalized ones. Amend 01d4bf299a (#1296) --- imgui.h | 2 +- imgui_demo.cpp | 13 +++++++------ imgui_draw.cpp | 14 +++++++------- imgui_internal.h | 2 +- imgui_widgets.cpp | 7 +++++-- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/imgui.h b/imgui.h index facb99975..788061cd7 100644 --- a/imgui.h +++ b/imgui.h @@ -1927,7 +1927,7 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. 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_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, - ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. + 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. }; // Identify a mouse button. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 1d914b371..a131680bd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2045,10 +2045,12 @@ static void DemoWindowWidgetsProgressBars() if (ImGui::TreeNode("Progress Bars")) { // Animate a simple progress bar - static float progress = 0.0f, progress_dir = 1.0f; - progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; - if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } - if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + static float progress_accum = 0.0f, progress_dir = 1.0f; + progress_accum += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress_accum >= +1.1f) { progress_accum = +1.1f; progress_dir *= -1.0f; } + if (progress_accum <= -0.1f) { progress_accum = -0.1f; progress_dir *= -1.0f; } + + const float progress = IM_CLAMP(progress_accum, 0.0f, 1.0f); // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. @@ -2056,9 +2058,8 @@ static void DemoWindowWidgetsProgressBars() ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::Text("Progress Bar"); - float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f); char buf[32]; - sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); + sprintf(buf, "%d/%d", (int)(progress * 1753), 1753); ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b01e57358..64e88fb55 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5830,7 +5830,7 @@ begin: // - RenderBullet() // - RenderCheckMark() // - RenderArrowPointingAt() -// - RenderRectFilledRangeH() +// - RenderRectFilledInRangeH() // - RenderRectFilledWithHole() //----------------------------------------------------------------------------- // Function in need of a redesign (legacy mess) @@ -5913,15 +5913,15 @@ static inline float ImAcos01(float x) } // FIXME: Cleanup and move code to ImDrawList. -void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) +// - Before 2025-12-04: RenderRectFilledRangeH() with 'float x_start_norm, float x_end_norm` <- normalized +// - After 2025-12-04: RenderRectFilledInRangeH() with 'float x1, float x2' <- absolute coords!! +void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding) { - if (x_end_norm == x_start_norm) + if (x1 == x2) return; - if (x_start_norm > x_end_norm) - ImSwap(x_start_norm, x_end_norm); - ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); - ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); + ImVec2 p0 = ImVec2(x1, rect.Min.y); + ImVec2 p1 = ImVec2(x2, rect.Max.y); if (rounding == 0.0f) { draw_list->AddRectFilled(p0, p1, col, 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index 318d013d4..2da881038 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3587,7 +3587,7 @@ namespace ImGui IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); - IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); // Widgets: Text diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2e05af1c6..85e77f041 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1440,7 +1440,10 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over // Render RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); - RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); + float fill_x0 = ImLerp(bb.Min.x, bb.Max.x, fill_n0); + float fill_x1 = ImLerp(bb.Min.x, bb.Max.x, fill_n1); + if (fill_x0 < fill_x1) + RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_x0, fill_x1, style.FrameRounding); // Default displaying the fraction as percentage string, but user can override it // Don't display text for indeterminate bars by default @@ -1456,7 +1459,7 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over ImVec2 overlay_size = CalcTextSize(overlay, NULL); if (overlay_size.x > 0.0f) { - float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x; + float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : fill_x1 + style.ItemSpacing.x; RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); } } From d71091a95761e71262162150a3c164848d65c7a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Dec 2025 16:27:54 +0100 Subject: [PATCH 21/28] Internals: amend RenderRectFilledRangeH() to avoid shadowed variables. --- imgui_draw.cpp | 8 ++++---- imgui_internal.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 64e88fb55..bee1503de 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5915,13 +5915,13 @@ static inline float ImAcos01(float x) // FIXME: Cleanup and move code to ImDrawList. // - Before 2025-12-04: RenderRectFilledRangeH() with 'float x_start_norm, float x_end_norm` <- normalized // - After 2025-12-04: RenderRectFilledInRangeH() with 'float x1, float x2' <- absolute coords!! -void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding) +void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float fill_x0, float fill_x1, float rounding) { - if (x1 == x2) + if (fill_x0 > fill_x1) return; - ImVec2 p0 = ImVec2(x1, rect.Min.y); - ImVec2 p1 = ImVec2(x2, rect.Max.y); + ImVec2 p0 = ImVec2(fill_x0, rect.Min.y); + ImVec2 p1 = ImVec2(fill_x1, rect.Max.y); if (rounding == 0.0f) { draw_list->AddRectFilled(p0, p1, col, 0.0f); diff --git a/imgui_internal.h b/imgui_internal.h index 2da881038..c1fcfbaf4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3587,7 +3587,7 @@ namespace ImGui IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); IMGUI_API void RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz); IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); - IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x1, float x2, float rounding); + IMGUI_API void RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float fill_x0, float fill_x1, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); // Widgets: Text From fee06a92ea7a21f3e5ebf77a30a3564bcb85cb43 Mon Sep 17 00:00:00 2001 From: achabense <60953653+achabense@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:36:16 +0800 Subject: [PATCH 22/28] Text: fixed word-wrapping function reading from *text_end when passed a string range. (#9107) Likely caused by 4d4889bf1b (#5720) --- docs/CHANGELOG.txt | 3 +++ imgui_draw.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 14ecb0dd0..ecd57ffbb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,9 @@ Other Changes: between hard minimum window size and table minimum size). - Fixed an issue where submitting non-integer row heights would eventually advance table parent layout by +0/+1 depending on its visibility. +- Text: + - Fixed low-level word-wrapping function reading from *text_end when passed + a string range. (#9107) [@achabense] - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be noticeable by user but detected by sanitizers). (#9089) [@judicaelclair] - Debug Tools: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index bee1503de..1b83fea92 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5364,7 +5364,7 @@ const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_e if ((flags & ImDrawTextFlags_WrapKeepBlanks) == 0) while (text < text_end && ImCharIsBlankA(*text)) text++; - if (*text == '\n') + if (text < text_end && *text == '\n') text++; return text; } From e2c49530cb677fa68ec56d5292575b861adc3596 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 13:42:15 +0100 Subject: [PATCH 23/28] ImDrawList: harden/clarify static asserts about ImDrawCmd / ImDrawCmdHeader layout. --- imgui_draw.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1b83fea92..88e897ac3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -444,10 +444,13 @@ void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) // In the majority of cases, you would want to call PushClipRect() and PushTexture() after this. void ImDrawList::_ResetForNewFrame() { - // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. + // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory to match ImDrawCmdHeader. IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0); IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == sizeof(ImVec4)); IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureRef)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == offsetof(ImDrawCmdHeader, ClipRect)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == offsetof(ImDrawCmdHeader, TexRef)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == offsetof(ImDrawCmdHeader, VtxOffset)); if (_Splitter._Count > 1) _Splitter.Merge(this); From 8e67fe13a5a248d41575d61c44be7f95d2439a14 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 13:51:52 +0100 Subject: [PATCH 24/28] Fixed an assert in background dimming code, which could trigger after using gamepad/keyboard to move a window to another viewport. (#9053) Due to missing ->Active check the dimming could render behind a window which was not submitted during the frame, appending to left-over ImDrawList data and in some situations tripping on the assert in RenderDimmedBackgroundBehindWindow(). The assumption that the PushClipRect won't match previous command is arguably a bit fragile, but this is what the assert is for. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index cf6e555f0..463203200 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -85,6 +85,8 @@ Other Changes: Docking+Viewports Branch: +- Fixed an assert in background dimming code, which could trigger after using + gamepad/keyboard to move a window to another viewport. (#9053) [@lut0pia, @ocornut] - Backends: - Win32: viewports created by backend forcefully direct messages to DefWindowProcW() in order to support Unicode text input. (#9099, #3653, #5961) [@ulhc] diff --git a/imgui.cpp b/imgui.cpp index 272881c75..55af463c3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6025,15 +6025,16 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 // Draw behind window by moving the draw command at the FRONT of the draw list { // Draw list have been trimmed already, hence the explicit recreation of a draw command if missing. - // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate ImDrawCmd order. + // FIXME: This is a little bit complicated, solely to avoid creating/injecting an extra drawlist in drawdata. ImDrawList* draw_list = window->RootWindowDockTree->DrawList; draw_list->ChannelsMerge(); if (draw_list->CmdBuffer.Size == 0) draw_list->AddDrawCmd(); draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that) - draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col); ImDrawCmd cmd = draw_list->CmdBuffer.back(); - IM_ASSERT(cmd.ElemCount == 6); + IM_ASSERT(cmd.ElemCount == 0); + draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col); + cmd = draw_list->CmdBuffer.back(); draw_list->CmdBuffer.pop_back(); draw_list->CmdBuffer.push_front(cmd); draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command. @@ -6095,10 +6096,12 @@ static void ImGui::RenderDimmedBackgrounds() { // Draw dimming behind Ctrl+Tab target window and behind Ctrl+Tab UI window RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); - if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport) - RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport; - viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL; + if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Active && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport) + { + RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); + viewports_already_dimmed[1] = g.NavWindowingListWindow->Viewport; + } // Draw border around Ctrl+Tab target window ImGuiWindow* window = g.NavWindowingTargetAnim; From 9d4fafa6713fdbf8b6424a8b88c5eec1dba953a8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 14:16:14 +0100 Subject: [PATCH 25/28] Nav: removed an unnecessary FindWindowByName() call. Amend c7016c25e8. It's not clear to me why it was committed like that, maybe a leftover of wip code which used the pointer before Begin(). --- imgui.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e21353a62..3fe059b6e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -14555,13 +14555,12 @@ void ImGui::NavUpdateWindowingOverlay() if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) return; - if (g.NavWindowingListWindow == NULL) - g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay"); const ImGuiViewport* viewport = GetMainViewport(); SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); Begin("##NavWindowingOverlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + g.NavWindowingListWindow = g.CurrentWindow; if (g.ContextName[0] != 0) SeparatorText(g.ContextName); for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) From ded52c71d983114a4efa9fef8ef31393998689e1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 15:07:44 +0100 Subject: [PATCH 26/28] Debug Log: can output to debugger. Added ImGuiDebugLogFlags_OutputToDebugger. --- docs/CHANGELOG.txt | 1 + imgui.cpp | 14 ++++++++++++-- imgui_internal.h | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ecd57ffbb..595072275 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -72,6 +72,7 @@ Other Changes: - Debug Tools: - Debug Log: fixed incorrectly printing characters in IO log when submitting non-ASCII values to io.AddInputCharacter(). (#9099) + - Debug Log: can output to debugger on Windows. (#5855) - Backends: - SDL_GPU3: macOS version can use MSL shaders in order to support macOS 10.14+ (vs Metallib shaders requiring macOS 14+). Requires application calling diff --git a/imgui.cpp b/imgui.cpp index 3fe059b6e..3c87bd87c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -17560,14 +17560,23 @@ void ImGui::DebugLogV(const char* fmt, va_list args) g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); g.DebugLogBuf.appendfv(fmt, args); g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size()); + + const char* str = g.DebugLogBuf.begin() + old_size; if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY) - IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size); + IMGUI_DEBUG_PRINTF("%s", str); +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) + if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToDebugger) + { + ::OutputDebugStringA("[imgui] "); + ::OutputDebugStringA(str); + } +#endif #ifdef IMGUI_ENABLE_TEST_ENGINE // IMGUI_TEST_ENGINE_LOG() adds a trailing \n automatically const int new_size = g.DebugLogBuf.size(); const bool trailing_carriage_return = (g.DebugLogBuf[new_size - 1] == '\n'); if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTestEngine) - IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0), g.DebugLogBuf.begin() + old_size); + IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0), str); #endif } @@ -17647,6 +17656,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) if (BeginPopup("Outputs")) { CheckboxFlags("OutputToTTY", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTTY); + CheckboxFlags("OutputToDebugger", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToDebugger); #ifndef IMGUI_ENABLE_TEST_ENGINE BeginDisabled(); #endif diff --git a/imgui_internal.h b/imgui_internal.h index c1fcfbaf4..23680045d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2078,7 +2078,8 @@ enum ImGuiDebugLogFlags_ 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_OutputToTTY = 1 << 20, // Also send output to TTY - ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine + ImGuiDebugLogFlags_OutputToDebugger = 1 << 21, // Also send output to Debugger Console [Windows only] + ImGuiDebugLogFlags_OutputToTestEngine = 1 << 22, // Also send output to Dear ImGui Test Engine }; struct ImGuiDebugAllocEntry From a7ecbcdeba912ba8e945878edf6f0c18164ce1ad Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 4 Dec 2025 16:45:45 +0100 Subject: [PATCH 27/28] Shuffle ImGuiColorEditFlags flag values. ImGuiColorEditFlags_AlphaOpaque, ImGuiColorEditFlags_AlphaNoBg, ImGuiColorEditFlags_AlphaPreviewHalf, ImGuiColorEditFlags_AlphaBar. --- imgui.h | 8 ++++---- imgui_demo.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index 788061cd7..8f60c2fc8 100644 --- a/imgui.h +++ b/imgui.h @@ -1878,12 +1878,12 @@ enum ImGuiColorEditFlags_ // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. // - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. // - The new flags may be combined better and allow finer controls. - ImGuiColorEditFlags_AlphaOpaque = 1 << 11, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. - ImGuiColorEditFlags_AlphaNoBg = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. - ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. + ImGuiColorEditFlags_AlphaOpaque = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. + 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). - ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. + 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. ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // " diff --git a/imgui_demo.cpp b/imgui_demo.cpp index a131680bd..294efd748 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1726,7 +1726,7 @@ static void DemoWindowWidgetsDragsAndSliders() // Sliders static float slider_f = 0.5f; static int slider_i = 50; - const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround; + const ImGuiSliderFlags flags_for_sliders = (flags & ~ImGuiSliderFlags_WrapAround); ImGui::Text("Underlying float value: %f", slider_f); ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); From fa4b47c5e2b53dba5093384562440bd9a6bba2a0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Dec 2025 16:29:15 +0100 Subject: [PATCH 28/28] Added RGBA color markers to ColorEdit3/ColorEdit4 + opt-in ImGuiSliderFlags_ColorMarkers for Drags/Sliders. + Added ImGuiColorEditFlags_NoColorMarkers + Added style.ColorMarkerSize. --- docs/CHANGELOG.txt | 8 ++++++++ imgui.cpp | 14 ++++++++++++++ imgui.h | 9 ++++++++- imgui_demo.cpp | 10 +++++++++- imgui_internal.h | 1 + imgui_widgets.cpp | 33 ++++++++++++++++++++++++++------- 6 files changed, 66 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 595072275..894baad88 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,14 @@ Other Changes: between hard minimum window size and table minimum size). - Fixed an issue where submitting non-integer row heights would eventually advance table parent layout by +0/+1 depending on its visibility. +- ColorEdit: + - Added R/G/B/A color markers next to each component (enabled by default). + - Added ImGuiColorEditFlags_NoColorMarkers to disable them. + - Added style.ColorMarkerSize to configure width of color component markers. +- Sliders, Drags: + - Added ImGuiSliderFlags_ColorMarkers to opt-in adding R/G/B/A color markers + next to each components, in multi-components functions. + - Added a way to select a specific marker color. - Text: - Fixed low-level word-wrapping function reading from *text_end when passed a string range. (#9107) [@achabense] diff --git a/imgui.cpp b/imgui.cpp index 3c87bd87c..a321d357e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1453,6 +1453,7 @@ ImGuiStyle::ImGuiStyle() 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. + 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. 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. @@ -1520,6 +1521,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) DragDropTargetRounding = ImTrunc(DragDropTargetRounding * scale_factor); DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor); DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor); + ColorMarkerSize = ImTrunc(ColorMarkerSize * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor); @@ -3923,6 +3925,18 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) } } +// FIXME: Might move those to style if there is a real need. +static const ImU32 GColorMarkers[4] = { IM_COL32(240,20,20,255), IM_COL32(20,240,20,255), IM_COL32(20,20,240,255), IM_COL32(140,140,140,255) }; + +void ImGui::RenderColorComponentMarker(int component_idx, const ImRect& bb, float rounding) +{ + if (!(component_idx >= 0 && component_idx < 4) || (bb.Min.x + 1 >= bb.Max.x)) + return; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(GColorMarkers[component_idx]), bb.Min.x, ImMin(bb.Min.x + g.Style.ColorMarkerSize, bb.Max.x), rounding); +} + void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags) { ImGuiContext& g = *GImGui; diff --git a/imgui.h b/imgui.h index 8f60c2fc8..8b359d57f 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.6 WIP" -#define IMGUI_VERSION_NUM 19252 +#define IMGUI_VERSION_NUM 19253 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1873,6 +1873,7 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default) + ImGuiColorEditFlags_NoColorMarkers = 1 << 11, // // ColorEdit: disable rendering R/G/B/A color marker. May also be disabled globally by setting style.ColorMarkerSize = 0. // Alpha preview // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. @@ -1927,6 +1928,11 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. 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_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, + + // Color Markers + ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component. + ImGuiSliderFlags_ColorMarkersIndexShift_ = 13, // [Internal] DragScalar(), SliderScalar(): Pass ([0..3] << ImGuiSliderFlags_ColorMarkersIndexShift_) along with ImGuiSliderFlags_ColorMarkers to select an individual R/G/B/A color. + 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. }; @@ -2314,6 +2320,7 @@ struct ImGuiStyle float DragDropTargetRounding; // Radius of the drag and drop target frame. 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. + 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. 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. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 294efd748..9dd4c5591 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1096,8 +1096,9 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaOpaque", &base_flags, ImGuiColorEditFlags_AlphaOpaque); ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaNoBg", &base_flags, ImGuiColorEditFlags_AlphaNoBg); ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaPreviewHalf", &base_flags, ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::CheckboxFlags("ImGuiColorEditFlags_NoDragDrop", &base_flags, ImGuiColorEditFlags_NoDragDrop); ImGui::CheckboxFlags("ImGuiColorEditFlags_NoOptions", &base_flags, ImGuiColorEditFlags_NoOptions); ImGui::SameLine(); HelpMarker("Right-click on the individual color widget to show options."); + ImGui::CheckboxFlags("ImGuiColorEditFlags_NoDragDrop", &base_flags, ImGuiColorEditFlags_NoDragDrop); + ImGui::CheckboxFlags("ImGuiColorEditFlags_NoColorMarkers", &base_flags, ImGuiColorEditFlags_NoColorMarkers); ImGui::CheckboxFlags("ImGuiColorEditFlags_HDR", &base_flags, ImGuiColorEditFlags_HDR); ImGui::SameLine(); HelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit"); @@ -1710,9 +1711,12 @@ static void DemoWindowWidgetsDragsAndSliders() ImGui::SameLine(); HelpMarker("Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic."); ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround); ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)"); + ImGui::CheckboxFlags("ImGuiSliderFlags_ColorMarkers", &flags, ImGuiSliderFlags_ColorMarkers); + //ImGui::CheckboxFlags("ImGuiSliderFlags_ColorMarkersG", &flags, 1 << ImGuiSliderFlags_ColorMarkersIndexShift_); // Not explicitly documented but possible. // Drags static float drag_f = 0.5f; + static float drag_f4[4]; static int drag_i = 50; ImGui::Text("Underlying float value: %f", drag_f); ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags); @@ -1722,14 +1726,17 @@ static void DemoWindowWidgetsDragsAndSliders() //ImGui::DragFloat("DragFloat (0 -> 0)", &drag_f, 0.005f, 0.0f, 0.0f, "%.3f", flags); // To test ClampZeroRange //ImGui::DragFloat("DragFloat (100 -> 100)", &drag_f, 0.005f, 100.0f, 100.0f, "%.3f", flags); ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags); + ImGui::DragFloat4("DragFloat4 (0 -> 1)", drag_f4, 0.005f, 0.0f, 1.0f, "%.3f", flags); // Multi-component item, mostly here to document the effect of ImGuiSliderFlags_ColorMarkers. // Sliders static float slider_f = 0.5f; + static float slider_f4[4]; static int slider_i = 50; const ImGuiSliderFlags flags_for_sliders = (flags & ~ImGuiSliderFlags_WrapAround); ImGui::Text("Underlying float value: %f", slider_f); ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); + ImGui::SliderFloat4("SliderFloat4 (0 -> 1)", slider_f4, 0.0f, 1.0f, "%.3f", flags); // Multi-component item, mostly here to document the effect of ImGuiSliderFlags_ColorMarkers. ImGui::TreePop(); } @@ -8444,6 +8451,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1); SeparatorText("Widgets"); + SliderFloat("ColorMarkerSize", &style.ColorMarkerSize, 0.0f, 8.0f, "%.0f"); Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); diff --git a/imgui_internal.h b/imgui_internal.h index 23680045d..849e48897 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3575,6 +3575,7 @@ namespace ImGui IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); 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(int component_idx, const ImRect& bb, 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 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 85e77f041..086a64c8d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2756,7 +2756,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding); + if ((flags & ImGuiSliderFlags_ColorMarkers) && style.ColorMarkerSize > 0.0f) + RenderColorComponentMarker(flags >> ImGuiSliderFlags_ColorMarkersIndexShift_, frame_bb, style.FrameRounding); + RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding); // Drag behavior const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags); @@ -2794,7 +2797,12 @@ bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); - value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); + + ImGuiSliderFlags flags_for_component = flags; + if ((flags & ImGuiSliderFlags_ColorMarkers) && (flags & (0x03 << ImGuiSliderFlags_ColorMarkersIndexShift_)) == 0 && (i < 4)) + flags_for_component |= (i << ImGuiSliderFlags_ColorMarkersIndexShift_); + + value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags_for_component); PopID(); PopItemWidth(); p_data = (void*)((char*)p_data + type_size); @@ -3342,7 +3350,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding); + if ((flags & ImGuiSliderFlags_ColorMarkers) && style.ColorMarkerSize > 0.0f) + RenderColorComponentMarker(flags >> ImGuiSliderFlags_ColorMarkersIndexShift_, frame_bb, style.FrameRounding); + RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding); // Slider behavior ImRect grab_bb; @@ -3386,7 +3397,12 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i PushID(i); if (i > 0) SameLine(0, g.Style.ItemInnerSpacing.x); - value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); + + ImGuiSliderFlags flags_for_component = flags; + if ((flags & ImGuiSliderFlags_ColorMarkers) && (flags & (0x03 << ImGuiSliderFlags_ColorMarkersIndexShift_ )) == 0 && (i < 4)) + flags_for_component |= (i << ImGuiSliderFlags_ColorMarkersIndexShift_); + + value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags_for_component); PopID(); PopItemWidth(); v = (void*)((char*)v + type_size); @@ -5784,8 +5800,10 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag { // RGB/HSV 0..255 Sliders const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1); + const float w_per_component = IM_TRUNC(w_items / components); + const bool draw_color_marker = (flags & (ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_NoColorMarkers)) == 0; - const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + const bool hide_prefix = draw_color_marker || (w_per_component <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); static const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; static const char* fmt_table_int[3][4] = { @@ -5811,14 +5829,15 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag prev_split = next_split; // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. + ImGuiSliderFlags drag_flags = draw_color_marker ? (ImGuiSliderFlags_ColorMarkers | (n << ImGuiSliderFlags_ColorMarkersIndexShift_)) : ImGuiSliderFlags_None; if (flags & ImGuiColorEditFlags_Float) { - value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n], drag_flags); value_changed_as_float |= value_changed; } else { - value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n], drag_flags); } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);