diff --git a/backends/imgui_impl_dx10.cpp b/backends/imgui_impl_dx10.cpp index 646f9a450..18bd637f3 100644 --- a/backends/imgui_impl_dx10.cpp +++ b/backends/imgui_impl_dx10.cpp @@ -18,6 +18,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-04-23: DirectX10: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplDX10_RenderState. (#9378) // 2026-01-19: DirectX10: Added 'SamplerNearest' in ImGui_ImplDX10_RenderState. Renamed 'SamplerDefault' to 'SamplerLinear'. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. @@ -112,8 +113,8 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device* // Setup viewport D3D10_VIEWPORT vp = {}; - vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; diff --git a/backends/imgui_impl_dx11.cpp b/backends/imgui_impl_dx11.cpp index 7d45d8ba7..64aaed799 100644 --- a/backends/imgui_impl_dx11.cpp +++ b/backends/imgui_impl_dx11.cpp @@ -118,8 +118,8 @@ static void ImGui_ImplDX11_SetupRenderState(const ImDrawData* draw_data, ID3D11D // Setup viewport D3D11_VIEWPORT vp = {}; - vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; - vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.Width = (float)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (float)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0; diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 069e59b29..af0aae730 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -23,6 +23,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-08-05: DirectX12: *BREAKING CHANGE* Removed support for legacy `ImGui_ImplDX12_Init()` signature obsoleted in 1.91.6 (2024-11-15) because it needed to forcefully disable support for ImGuiBackendFlags_RendererHasTextures. (#9487) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2025-10-23: [Docking] DirectX12: Fixed an issue in synchronization logic improving rendering throughput for secondary viewports. (#8961, #9025) @@ -258,8 +259,8 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic // Setup viewport D3D12_VIEWPORT vp = {}; - vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x; - vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y; + vp.Width = (float)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + vp.Height = (float)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = vp.TopLeftY = 0.0f; diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index dca2ab878..80b47fc03 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -18,6 +18,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Metal: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-08-06: Metal: fixed resizing windows losing framebuffer scale. (#6828, #8856) // 2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378) @@ -172,8 +173,8 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, idDisplaySize.x * draw_data->FramebufferScale.x), - .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .width = (double)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f), + .height = (double)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f), .znear = 0.0, .zfar = 1.0 }; @@ -212,8 +213,8 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalContext* ctx = bd->SharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 6044c237b..301d5ddd9 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-08-06: Metal 4: fixed resizing windows losing framebuffer scale. (#6828, #8856) // 2026-07-07: Metal 4: Added metal-cpp support. (#9461) // 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) @@ -181,8 +182,8 @@ static void ImGui_ImplMetal4_SetupRenderState(ImDrawData* draw_data, idDisplaySize.x * draw_data->FramebufferScale.x), - .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y), + .width = (double)(int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f), + .height = (double)(int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f), .znear = 0.0, .zfar = 1.0 }; @@ -229,8 +230,8 @@ void ImGui_ImplMetal4_RenderDrawData(ImDrawData* draw_data, idSharedMetalContext; // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; diff --git a/backends/imgui_impl_opengl2.cpp b/backends/imgui_impl_opengl2.cpp index 18ad713bb..6dcd5b9d7 100644 --- a/backends/imgui_impl_opengl2.cpp +++ b/backends/imgui_impl_opengl2.cpp @@ -28,6 +28,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-04-23: OpenGL: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-03-12: OpenGL: Fixed invalid assert in ImGui_ImplOpenGL3_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295) @@ -174,8 +175,8 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width == 0 || fb_height == 0) return; diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index 44b33be72..05f604d47 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -25,6 +25,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-15: OpenGL: Backup and restore GL_UNPACK_ROW_LENGTH and GL_UNPACK_ALIGNMENT in UpdateTexture() to avoid corrupting caller GL state. (#8802, #9473) // 2026-06-17: OpenGL: Expose selected render state in ImGui_ImplOpenGL3_RenderState, Allowing to dynamically select between use of glBindSampler() and glTexParameter(). You can access in 'void* platform_io.Renderer_RenderState' during rendering. // 2026-06-03: OpenGL: GLSL version detection assume GLSL 410 when GL context is 4.1. Fixes an issue running on macOS with Wine. (#9427, #6577) @@ -461,8 +462,8 @@ static void ImGui_ImplOpenGL3_DrawCallback_SetSamplerNearest(const ImDrawList*, void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; diff --git a/backends/imgui_impl_sdlgpu3.cpp b/backends/imgui_impl_sdlgpu3.cpp index 77e38db6a..7436f8930 100644 --- a/backends/imgui_impl_sdlgpu3.cpp +++ b/backends/imgui_impl_sdlgpu3.cpp @@ -24,6 +24,7 @@ // CHANGELOG // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. Obsoleting samplers from ImGui_ImplSDLGPU3_RenderState. (#9378) // 2026-03-19: Fixed issue in ImGui_ImplSDLGPU3_DestroyTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2026-02-25: Removed unnecessary call to SDL_WaitForGPUIdle when releasing vertex/index buffers. (#9262) @@ -165,8 +166,8 @@ static void CreateOrResizeBuffers(SDL_GPUBuffer** buffer, SDL_GPUTransferBuffer* void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0) return; @@ -231,8 +232,8 @@ static void ImGui_ImplSDLGPU3_DrawCallback_SetSamplerNearest(const ImDrawList*, void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index d6d24b39c..e50181e02 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -29,6 +29,8 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) +// 2026-09-03: Added for support for multiple Vulkan contexts with custom loaders. (#6616) // 2026-07-15: [Docking] Vulkan: fixed use-after-free when using multi-viewport with dynamic rendering path: deep-copy SurfaceFormat.format into the persistent buffer instead of storing a pointer to the viewport's wd->SurfaceFormat which dangles after the viewport is destroyed. (#9390, #9468) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-04-22: *BREAKING CHANGE* redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. This change allows us to facilitate changing samplers, in line with other backends. @@ -241,10 +243,28 @@ static bool g_FunctionsLoaded = true; IMGUI_VULKAN_FUNC_MAP_MACRO(vkUpdateDescriptorSets) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkWaitForFences) +struct ImGui_ImplVulkan_Functions +{ // Define function pointers -#define IMGUI_VULKAN_FUNC_DEF(func) static PFN_##func func; +#define IMGUI_VULKAN_FUNC_DEF(func) PFN_##func pfn_##func; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_DEF) #undef IMGUI_VULKAN_FUNC_DEF +}; + +// Globally available function pointers used for initialization of the backend data +static ImGui_ImplVulkan_Functions ImGuiImplVulkanFuncs; + +// Multi Vulkan contexts support: wrap each Vulkan function pointer to properly dispatch it using the current backend data +static ImGui_ImplVulkan_Functions& ImGui_ImplVulkan_GetFunctions(); +#define IMGUI_VULKAN_FUNC_WRAPPER(func) \ + template \ + auto func(Args&&... args) -> decltype(ImGui_ImplVulkan_GetFunctions().pfn_##func(args...)) \ + { \ + return ImGui_ImplVulkan_GetFunctions().pfn_##func(args...); \ + } +IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_WRAPPER) +#undef IMGUI_VULKAN_FUNC_WRAPPER + #endif // IMGUI_IMPL_VULKAN_USE_LOADER #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING @@ -326,6 +346,11 @@ struct ImGui_ImplVulkan_Data // Render buffers for main window ImGui_ImplVulkan_WindowRenderBuffers MainWindowRenderBuffers; + // Vulkan function pointers loaded for this context +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER + ImGui_ImplVulkan_Functions Functions; +#endif + ImGui_ImplVulkan_Data() { memset((void*)this, 0, sizeof(*this)); @@ -464,6 +489,16 @@ static ImGui_ImplVulkan_Data* ImGui_ImplVulkan_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplVulkan_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER +// Get the Vulkan function pointers used by the current backend. +// If no backend is available, return the currently loaded global function pointers (ImGuiImplVulkanFuncs). +static ImGui_ImplVulkan_Functions& ImGui_ImplVulkan_GetFunctions() +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + return bd ? bd->Functions : ImGuiImplVulkanFuncs; +} +#endif + static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -559,7 +594,7 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline constants[1] = 2.0f / draw_data->DisplaySize.y; constants[2] = -1.0f - draw_data->DisplayPos.x * constants[0]; // Translate constants[3] = -1.0f - draw_data->DisplayPos.y * constants[1]; - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4, constants); + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, (uint32_t)sizeof(float) * 4, constants); // Setup sampler vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); @@ -583,8 +618,8 @@ void ImGui_ImplVulkan_DrawCallback_SetSamplerCustom(const ImDrawList*, const ImD void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0) return; @@ -1354,8 +1389,8 @@ bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction( #ifdef IMGUI_IMPL_VULKAN_USE_LOADER #define IMGUI_VULKAN_FUNC_LOAD(func) \ - func = reinterpret_cast(loader_func(#func, user_data)); \ - if (func == nullptr) \ + ImGuiImplVulkanFuncs.pfn_##func = reinterpret_cast(loader_func(#func, user_data)); \ + if (ImGuiImplVulkanFuncs.pfn_##func == nullptr) \ return false; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_LOAD) #undef IMGUI_VULKAN_FUNC_LOAD @@ -1424,6 +1459,9 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) IM_ASSERT(info->PipelineInfoMain.RenderPass == VK_NULL_HANDLE && info->PipelineInfoForViewports.RenderPass == VK_NULL_HANDLE); bd->VulkanInitInfo = *info; +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER + bd->Functions = ImGuiImplVulkanFuncs; +#endif VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(info->PhysicalDevice, &properties); diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 452fe3d18..7827bf8fc 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -22,6 +22,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-08-31: Update to compile with wgpu-native 29.0. (#9377, #9532, #9530) // 2026-08-18: Fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport() when FramebufferScale is >1.0f. (#9515, #8628) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -438,8 +439,8 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas } // Setup viewport - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); wgpuRenderPassEncoderSetViewport(ctx, 0, 0, (float)fb_width, (float)fb_height, 0, 1); // Bind shader and vertex buffers @@ -462,8 +463,8 @@ static void ImGui_ImplWGPU_DrawCallback_SetSamplerNearest(const ImDrawList*, con void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder) { // Avoid rendering when minimized - int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) return; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 28e7231f1..89f150d16 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -42,9 +42,9 @@ HOW TO UPDATE? Breaking Changes: - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in - favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. It is + favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. It is easier to tweak + this allows us to easily scale error threshold on high density screens. - - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). + - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). - Backends: - DirectX12: Removed support for legacy `ImGui_ImplDX12_Init()` signature, which was obsoleted in 1.91.6 (December 2014), because it needed to forcefully disable support @@ -55,6 +55,8 @@ Other Changes: - Drags, Sliders: - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] + - Ongoing drags may be cancelled using Right-Click, Escape or Gamepad Triangle. + Cancelling reverts to the initial value. (#8564, #9534) - TreeNode: - Fixed issues/asserts with 32+ deep trees when using ImGuiTreeNodeFlags_DrawLinesXXX features or other features requiring stack storage. (#9509) [@lasrod] @@ -67,7 +69,15 @@ Other Changes: disabled color buttons have the same color as non-disabled oness. (#9511) - ColorButton: fixed issues/inconsistencies with checkerboard rendering when combining color alpha and global style alpha. (#9511) [@enjmiah, @ocornut] +- Popups: + - Amended context menu right-click opening code to check that the right-click + hasn't been owned by another items. This is more correct and also necessary + to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) - Misc: + - Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values. + Supported by: Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), ColorXXX() + and Combo() functions. + The scoped flag is designed for usage by advanced property editors. (#5518, #5677, #6865) - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset every frame when cumulated session time is very large (e.g. a few days). [@lailoken] - Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC) @@ -75,6 +85,8 @@ Other Changes: trivial functions when stepping into. - Tagged various trivial functions using `IM_NODEBUGSTEP`: `ImVec2()`, `ImVec4()` etc. and various operators. + - IO: restore default clipboard/ime/shell platform handlers on ClearPlatformHandlers(), fixing + issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] - ImDrawList: - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - AA Fringe is scaled accordingly. @@ -82,10 +94,13 @@ Other Changes: - Reworked assert in PushTexture() to allow convenience grace period of using a texture that was queue for destroy during the frame. Make it safe to stick to an existing texture during the frame. (#9528, #8465) +- Demo: + - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - SDL2: fixed querying framebuffer scale/density when using Metal without going through a SDL_Renderer. (#5592) [@lailoken] + - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] diff --git a/examples/example_apple_opengl2/main.mm b/examples/example_apple_opengl2/main.mm index 3cee24a46..98e6a29fa 100644 --- a/examples/example_apple_opengl2/main.mm +++ b/examples/example_apple_opengl2/main.mm @@ -143,8 +143,8 @@ ImDrawData* draw_data = ImGui::GetDrawData(); [[self openGLContext] makeCurrentContext]; - GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); glViewport(0, 0, width, height); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); diff --git a/examples/example_apple_opengl3/main.mm b/examples/example_apple_opengl3/main.mm index 339a93789..818262dda 100644 --- a/examples/example_apple_opengl3/main.mm +++ b/examples/example_apple_opengl3/main.mm @@ -143,8 +143,8 @@ ImDrawData* draw_data = ImGui::GetDrawData(); [[self openGLContext] makeCurrentContext]; - GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); - GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + GLsizei width = (GLsizei)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x + 0.5f); + GLsizei height = (GLsizei)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y + 0.5f); glViewport(0, 0, width, height); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClear(GL_COLOR_BUFFER_BIT); diff --git a/imgui.cpp b/imgui.cpp index 3e3f14d4c..c0b8a8a55 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -402,8 +402,8 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos) - likewise io.MousePos and GetMousePos() will use OS coordinates. If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. - - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTesselationMaxError` (default 1.12)` which is in Pixels unit. - - style.CurveTesselationMaxError == sqrf(style.CurveTessellationTol). + - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. + - style.CurveTessellationMaxError == sqrf(style.CurveTessellationTol). - 2026/07/20 (1.92.9) - DragXXX, SliderXXX, InputScalar: with `ImGuiItemFlags_LiveEditOnInputScalar` now defaulting to being disabled: inputting a value with the keyboard doesn't write intermediate values to backing variable. (#9476) - Before: DragFloat() with user typing "123" --> write back 1, then 12, then 123. @@ -1404,10 +1404,9 @@ static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSetti static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); // Platform Dependents default implementation for ImGuiPlatformIO functions +static void InitializeDefaultPlatformHandlers(ImGuiPlatformIO& platform_io); static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx); static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text); -static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); -static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path); namespace ImGui { @@ -1603,7 +1602,7 @@ ImGuiStyle::ImGuiStyle() DockingSeparatorSize = 2.0f; // Thickness of resizing border between docked windows MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. - // Rendering & Tesselation + // Rendering & Tessellation AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). @@ -4464,6 +4463,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + MixedValueLabel = "-"; InputTextPasswordFontBackupFlags = ImFontFlags_None; InputTextReactivateId = 0; TempInputId = 0; @@ -4572,10 +4572,7 @@ void ImGui::Initialize() LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_COUNTOF(GLocalizationEntriesEnUS)); // Setup default ImGuiPlatformIO clipboard/IME handlers. - g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations - g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; - g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; - g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; + InitializeDefaultPlatformHandlers(g.PlatformIO); // Setup session starting date #ifndef IMGUI_DISABLE_TIME_FUNCTIONS @@ -13475,7 +13472,7 @@ bool ImGui::IsPopupOpenRequestForItem(ImGuiPopupFlags popup_flags, ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(mouse_button, id) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) return true; if (g.NavOpenContextMenuItemId == id && (IsItemFocused() || id == g.CurrentWindow->MoveId)) return true; @@ -13486,7 +13483,7 @@ bool ImGui::IsPopupOpenRequestForWindow(ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(mouse_button, ImGuiKeyOwner_NoOwner) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) return true; if (g.NavOpenContextMenuWindowId && g.CurrentWindow->ID) @@ -13557,9 +13554,9 @@ bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flag ImGuiWindow* window = g.CurrentWindow; if (!str_id) str_id = "void_context"; - ImGuiID id = window->GetID(str_id); + ImGuiID id = window->GetID(str_id); // FIXME: Use a global ID? ImGuiMouseButton mouse_button = GetMouseButtonFromPopupFlags(popup_flags); - if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + if (IsMouseReleased(mouse_button, id) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) if (GetTopMostPopupModal() == NULL) OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); @@ -16890,6 +16887,7 @@ void ImGuiPlatformIO::ClearPlatformHandlers() Platform_OnChangedViewport = NULL; Platform_GetWindowWorkAreaInsets = NULL; Platform_CreateVkSurface = NULL; + InitializeDefaultPlatformHandlers(*this); // Prevents losing the functionality provided by Platform_*_DefaultImpl on backend re-creation. } void ImGuiPlatformIO::ClearRendererHandlers() @@ -22167,6 +22165,15 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG #endif // Default IME handlers +// Setup default ImGuiPlatformIO clipboard/IME handlers +static void InitializeDefaultPlatformHandlers(ImGuiPlatformIO& platform_io) +{ + platform_io.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; + platform_io.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; + platform_io.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; + platform_io.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; +} + //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- diff --git a/imgui.h b/imgui.h index 7a34d1827..09440dec3 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.93.0 WIP" -#define IMGUI_VERSION_NUM 19295 +#define IMGUI_VERSION_NUM 19296 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 #define IMGUI_HAS_VIEWPORT // In 'docking' WIP branch. @@ -1305,6 +1305,7 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). + ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. Only supported by some widgets: Checkbox, RadioButton, Sliders and Drags. //--------------------------------------------------------------------------------- // LiveEdit refers to applying edits to backing variables _while_ typing a value using the keyboard. @@ -2472,7 +2473,7 @@ struct ImGuiStyle float DockingSeparatorSize; // Thickness of resizing border between docked windows float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. - // Rendering & Tesselation + // Rendering & Tessellation bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5ed279d7a..0acd3ea90 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -87,6 +87,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsListBoxes() // [SECTION] DemoWindowWidgetsLiveEdit() +// [SECTION] DemoWindowWidgetsMixedValues() // [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsPlotting() // [SECTION] DemoWindowWidgetsProgressBars() @@ -2091,7 +2092,7 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) { if (ImGui::TreeNode("Live Edit Flags")) { - IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs"); + IMGUI_DEMO_MARKER("Widgets/Live Edit Flags"); ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing."); @@ -2121,6 +2122,61 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data) } } +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgetsMixedValues() +//----------------------------------------------------------------------------- + +static void DemoWindowWidgetsMixedValues() +{ + if (ImGui::TreeNode("Mixed Values")) + { + // This is designed for advanced property editors which are generally reusable and data-driven. + HelpMarker("Using ImGuiItemFlags_MixedValue."); + + static float items[3] = { 12.0f, 0.0f, 0.0f }; + float* item_ref = &items[0]; + + ImGui::SeparatorText("Scalar/Text Widgets"); + const bool is_mixed = memcmp(&items[0], &items[1], sizeof(float)) != 0 || memcmp(&items[0], &items[2], sizeof(float)) != 0; + + // Demonstrate Drags, Sliders, Inputs + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, is_mixed); + bool edited = false; + edited |= ImGui::DragFloat("DragFloat", item_ref); + edited |= ImGui::SliderFloat("SliderFloat", item_ref, 0.0f, 100.0f); + edited |= ImGui::InputFloat("InputFloat", item_ref, 1.0f); + if (edited) + for (float& item : items) + if (&item != item_ref) + item = *item_ref; + ImGui::PopItemFlag(); + + ImGui::Text("Underlying data:"); + ImGui::InputFloat("item 0 (ref)", &items[0]); + ImGui::InputFloat("item 1", &items[1]); + ImGui::InputFloat("item 2", &items[2]); + + // Demonstrate Checkbox(), RadioButton(), Combo(), ColorEdit4() + ImGui::SeparatorText("Others Widgets"); + ImGui::Text("(note: edits are not applied in this demo)"); // <-- Would need more state tracking. + bool b_on = true, b_off = false; + ImGui::Checkbox("Checkbox On", &b_on); + ImGui::Checkbox("Checkbox Off", &b_off); + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); + ImGui::Checkbox("Checkbox Mixed", &b_off); + ImGui::RadioButton("RadioButton Mixed", true); + ImGui::SameLine(); + ImGui::RadioButton("RadioButton Mixed##2", true); // Showing 2 radio buttons makes the example more clear + int combo_idx = 0; + ImGui::Combo("Combo", &combo_idx, "One\0Two\0Three\0"); + ImVec4 color(0.5f, 0.5f, 0.5f, 0.5f); + ImGui::ColorEdit4("ColorEdit4", &color.x); + ImGui::PopItemFlag(); + + ImGui::TreePop(); + } +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsMultiComponents() //----------------------------------------------------------------------------- @@ -2301,12 +2357,14 @@ static void DemoWindowWidgetsQueryingStatuses() }; static int item_type = 4; static bool item_disabled = false; + static bool item_mixedvalue = false; static bool liveedit_flags_override = false; static ImGuiItemFlags liveedit_flags = 0; ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); + ImGui::Checkbox("Item MixedValue", &item_mixedvalue); ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override); ImGui::SameLine(); if (!liveedit_flags_override) @@ -2331,6 +2389,8 @@ static void DemoWindowWidgetsQueryingStatuses() static char str[16] = {}; if (item_disabled) ImGui::BeginDisabled(true); + if (item_mixedvalue) + ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true); if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) @@ -2414,6 +2474,8 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::PopItemFlag(); ImGui::PopItemFlag(); } + if (item_mixedvalue) + ImGui::PopItemFlag(); if (item_disabled) ImGui::EndDisabled(); @@ -4591,6 +4653,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsImages(); DemoWindowWidgetsListBoxes(); DemoWindowWidgetsLiveEdit(demo_data); + DemoWindowWidgetsMixedValues(); DemoWindowWidgetsMultiComponents(); DemoWindowWidgetsPlotting(); DemoWindowWidgetsProgressBars(); diff --git a/imgui_internal.h b/imgui_internal.h index 038ee34fa..bd1290d06 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1004,7 +1004,6 @@ enum ImGuiItemFlagsPrivate_ { // Controlled by user ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. - ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). @@ -2502,6 +2501,7 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information + const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfoId; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index eb94bb3a4..8b05fe5aa 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1405,7 +1405,7 @@ bool ImGui::RadioButton(const char* label, bool active) RenderNavCursor(total_bb, id); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); - if (active) + if (active && (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) == 0) { const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); @@ -2183,7 +2183,9 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo // Call the getter to obtain the preview string which is a parameter to BeginCombo() const char* preview_value = NULL; - if (*current_item >= 0 && *current_item < items_count) + if ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_MixedValue) + preview_value = ""; + else if (*current_item >= 0 && *current_item < items_count) preview_value = getter(user_data, *current_item); // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. @@ -2667,19 +2669,39 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const return true; } +static bool ShortcutsForCancel(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + bool is_cancel_with_keyboard = ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, id); + bool is_cancel_with_gamepad = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0 && ImGui::Shortcut(ImGuiKey_NavGamepadCancel, ImGuiInputFlags_None, id); + bool is_cancel_with_mouse = ImGui::IsMouseReleased(ImGuiMouseButton_Right, id); + if (is_cancel_with_mouse) + ImGui::SetKeyOwner(ImGuiKey_MouseRight, id); + return is_cancel_with_keyboard || is_cancel_with_gamepad || is_cancel_with_mouse; +} + bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + // Things we can do easily outside the DragBehaviorT<> template, saves code generation. ImGuiContext& g = *GImGui; if (g.ActiveId == id) { - // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) ClearActiveID(); else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) ClearActiveID(); + if (ShortcutsForCancel(id) && g.ActiveId == id) //-V560 + { + // Canceling action reverts to initial value + size_t data_size = DataTypeGetInfo(data_type)->Size; + bool value_changed = memcmp(p_v, &g.ActiveIdValueOnActivation, data_size); + memcpy(p_v, &g.ActiveIdValueOnActivation, data_size); + ClearActiveID(); + return value_changed; + } } if (g.ActiveId != id) return false; @@ -2802,8 +2824,9 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, MarkItemEdited(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); @@ -3092,13 +3115,13 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T { result = ImLerp(v_min, v_max, t); } - else if (t < 1.0) + else // t is already < 1.0 here { // - For integer values we want the clicking position to match the grab box so we round above // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t; + FLOATTYPE v_new_off_f = (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min) * (FLOATTYPE)t; result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); } } @@ -3147,37 +3170,24 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ float clicked_t = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse) { - if (!g.IO.MouseDown[0]) + const float mouse_abs_pos = g.IO.MousePos[axis]; + if (g.ActiveIdIsJustActivated) { - ClearActiveID(); - } - else - { - const float mouse_abs_pos = g.IO.MousePos[axis]; - if (g.ActiveIdIsJustActivated) - { - float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); - if (axis == ImGuiAxis_Y) - grab_t = 1.0f - grab_t; - const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); - const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. - g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; - } - if (slider_usable_sz > 0.0f) - clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (axis == ImGuiAxis_Y) - clicked_t = 1.0f - clicked_t; - set_new_value = true; + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. + g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; } + if (slider_usable_sz > 0.0f) + clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + set_new_value = true; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { - if (g.ActiveIdIsJustActivated) - { - g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation - g.SliderCurrentAccumDirty = false; - } - float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis); if (input_delta != 0.0f) { @@ -3205,11 +3215,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } float delta = g.SliderCurrentAccum; - if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) - { - ClearActiveID(); - } - else if (g.SliderCurrentAccumDirty) + if (g.SliderCurrentAccumDirty) { clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize); @@ -3235,7 +3241,6 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ else g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); } - g.SliderCurrentAccumDirty = false; } } @@ -3290,6 +3295,33 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() + // Things we can do easily outside the SliderBehaviorT<> template, saves code generation. + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) + { + if (g.ActiveIdIsJustActivated) + { + g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation + g.SliderCurrentAccumDirty = false; + } + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (ShortcutsForCancel(id) && g.ActiveId == id) //-V560 + { + // Canceling action reverts to initial value + size_t data_size = DataTypeGetInfo(data_type)->Size; + bool value_changed = memcmp(p_v, &g.ActiveIdValueOnActivation, data_size); + memcpy(p_v, &g.ActiveIdValueOnActivation, data_size); + ClearActiveID(); + return value_changed; + } + } + switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } @@ -3400,8 +3432,9 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); @@ -3554,8 +3587,9 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding + const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3793,7 +3827,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG // Only mark as edited if new value is different g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; - bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0 || (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue); if (value_changed) MarkItemEdited(id); return value_changed; @@ -3870,6 +3904,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); } } + if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) + value_changed |= ret; // Step buttons if (has_step_buttons) @@ -4835,6 +4871,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; if (is_resizable) IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! @@ -4921,7 +4958,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!is_multiline) { - if (flags & ImGuiInputTextFlags_AutoSelectAll) + if ((flags & ImGuiInputTextFlags_AutoSelectAll) || is_mixed) select_all = true; if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState))) select_all = true; @@ -4999,7 +5036,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Select the buffer to render. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; - bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed; // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) @@ -5426,7 +5463,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified - if (strcmp(state->TextSrc, buf) != 0) + if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)) { apply_new_text = state->TextSrc; apply_new_text_length = state->TextLen; @@ -5436,7 +5473,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { // Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert) - value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0); + value_changed = (validated || clear_active_id || revert_edit) && (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)); apply_new_text = value_changed ? state->TextSrc : NULL; apply_new_text_length = value_changed ? state->TextLen : 0; } @@ -5521,7 +5558,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Display hint when contents is empty // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368) - const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed; if (new_is_displaying_hint != is_displaying_hint) { if (is_password && !is_displaying_hint) @@ -5530,10 +5567,16 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); } - if (is_displaying_hint) + if (is_mixed && g.ActiveId != id && apply_new_text == NULL) + { + buf_display = g.MixedValueLabel; + buf_display_end = buf_display + strlen(g.MixedValueLabel); + render_cursor = render_selection = false; + } + else if (is_displaying_hint) { buf_display = hint; - buf_display_end = hint + ImStrlen(hint); + buf_display_end = buf_display + ImStrlen(buf_display); } else { @@ -6022,7 +6065,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + PushItemFlag(ImGuiItemFlags_MixedValue, false); value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + PopItemFlag(); } EndPopup(); } @@ -6297,6 +6342,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + PushItemFlag(ImGuiItemFlags_MixedValue, false); ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); @@ -6314,6 +6360,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } } PopItemFlag(); + PopItemFlag(); EndGroup(); } @@ -6532,14 +6579,20 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (flags & ImGuiColorEditFlags_InputHSV) ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); - ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); - float grid_step = ImMin(size.x, size.y) / 2.99f; - float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + const ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); + const float grid_step = ImMin(size.x, size.y) / 2.99f; + const float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; const float backup_alpha = g.Style.Alpha; if (g.DisabledStackSize > 0) g.Style.Alpha = g.DisabledAlphaBackup; // Cancel out effect of BeginDisabled() for color swatches. - if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) + if (is_mixed) + { + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); + RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + } + else if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb.Min.x + bb.Max.x) * 0.5f); if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) @@ -6576,7 +6629,9 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); else SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); + PushItemFlag(ImGuiItemFlags_MixedValue, false); ColorButton(desc_id, col, flags); + PopItemFlag(); SameLine(); TextEx("Color"); EndDragDropSource(); @@ -6620,7 +6675,9 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_; + PushItemFlag(ImGuiItemFlags_MixedValue, false); ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz); + PopItemFlag(); SameLine(); if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) {