From d8d58b038eb22e3d8f2591efcb228a5a1dfee291 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 8 Sep 2020 20:02:28 +0200 Subject: [PATCH 01/28] Backends, Examples: DX12: Clarify support for 32-bit building in project files and comments. (#301) --- docs/CHANGELOG.txt | 4 ++ .../example_win32_directx12/build_win32.bat | 3 +- .../example_win32_directx12.vcxproj | 4 ++ examples/imgui_examples.sln | 58 +++++++++++-------- examples/imgui_impl_dx12.cpp | 7 ++- examples/imgui_impl_dx12.h | 6 +- imconfig.h | 9 +-- 7 files changed, 58 insertions(+), 33 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9cec4446..092bd005 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -68,9 +68,13 @@ Other Changes: - Demo: Add simple InputText() callbacks demo (aside from the more elaborate ones in 'Examples->Console'). - Backends: Vulkan: Some internal refactor aimed at allowing multi-viewport feature to create their own render pass. (#3455, #3459) [@FunMiles] +- Backends: DX12: Clarified that imgui_impl_dx12 can be compiled on 32-bit systems by redefining + the ImTextureID to be 64-bit (e.g. '#define ImTextureID ImU64' in imconfig.h). (#301) - Examples: Vulkan: Reworked buffer resize handling, fix for Linux/X11. (#3390, #2626) [@RoryO] - Examples: Vulkan: Switch validation layer to use "VK_LAYER_KHRONOS_validation" instead of "VK_LAYER_LUNARG_standard_validation" which is deprecated (#3459) [@FunMiles] +- Examples: DX12: Added '#define ImTextureID ImU64' in project and build files to also allow building + on 32-bit systems. Added project to default Visual Studio solution file. (#301) ----------------------------------------------------------------------- diff --git a/examples/example_win32_directx12/build_win32.bat b/examples/example_win32_directx12/build_win32.bat index 2cd7fcdf..85a52b70 100644 --- a/examples/example_win32_directx12/build_win32.bat +++ b/examples/example_win32_directx12/build_win32.bat @@ -1,4 +1,5 @@ @REM Build for Visual Studio compiler. Run your copy of vcvars32.bat or vcvarsall.bat to setup command-line compiler. +@REM Important: to build on 32-bit systems, the DX12 back-ends needs '#define ImTextureID ImU64', so we pass it here. mkdir Debug -cl /nologo /Zi /MD /I .. /I ..\.. /I "%WindowsSdkDir%Include\um" /I "%WindowsSdkDir%Include\shared" /D UNICODE /D _UNICODE *.cpp ..\imgui_impl_dx12.cpp ..\imgui_impl_win32.cpp ..\..\imgui*.cpp /FeDebug/example_win32_directx12.exe /FoDebug/ /link d3d12.lib d3dcompiler.lib dxgi.lib +cl /nologo /Zi /MD /I .. /I ..\.. /I "%WindowsSdkDir%Include\um" /I "%WindowsSdkDir%Include\shared" /D ImTextureID=ImU64 /D UNICODE /D _UNICODE *.cpp ..\imgui_impl_dx12.cpp ..\imgui_impl_win32.cpp ..\..\imgui*.cpp /FeDebug/example_win32_directx12.exe /FoDebug/ /link d3d12.lib d3dcompiler.lib dxgi.lib diff --git a/examples/example_win32_directx12/example_win32_directx12.vcxproj b/examples/example_win32_directx12/example_win32_directx12.vcxproj index dabd6d84..f03bf760 100644 --- a/examples/example_win32_directx12/example_win32_directx12.vcxproj +++ b/examples/example_win32_directx12/example_win32_directx12.vcxproj @@ -87,6 +87,7 @@ Level4 Disabled ..\..;..;%(AdditionalIncludeDirectories) + ImTextureID=ImU64;_UNICODE;UNICODE;%(PreprocessorDefinitions) true @@ -100,6 +101,7 @@ Level4 Disabled ..\..;..;%(AdditionalIncludeDirectories) + ImTextureID=ImU64;_UNICODE;UNICODE;%(PreprocessorDefinitions) true @@ -115,6 +117,7 @@ true true ..\..;..;%(AdditionalIncludeDirectories) + ImTextureID=ImU64;_UNICODE;UNICODE;%(PreprocessorDefinitions) true @@ -132,6 +135,7 @@ true true ..\..;..;%(AdditionalIncludeDirectories) + ImTextureID=ImU64;_UNICODE;UNICODE;%(PreprocessorDefinitions) true diff --git a/examples/imgui_examples.sln b/examples/imgui_examples.sln index 49b2ff89..605b1f57 100644 --- a/examples/imgui_examples.sln +++ b/examples/imgui_examples.sln @@ -13,6 +13,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_glfw_opengl2", "exa EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_glfw_opengl3", "example_glfw_opengl3\example_glfw_opengl3.vcxproj", "{4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_win32_directx12", "example_win32_directx12\example_win32_directx12.vcxproj", "{B4CF9797-519D-4AFE-A8F4-5141A6B521D3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -21,14 +23,6 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|Win32.ActiveCfg = Debug|Win32 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|Win32.Build.0 = Debug|Win32 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|x64.ActiveCfg = Debug|x64 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|x64.Build.0 = Debug|x64 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|Win32.ActiveCfg = Release|Win32 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|Win32.Build.0 = Release|Win32 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|x64.ActiveCfg = Release|x64 - {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|x64.Build.0 = Release|x64 {4165A294-21F2-44CA-9B38-E3F935ABADF5}.Debug|Win32.ActiveCfg = Debug|Win32 {4165A294-21F2-44CA-9B38-E3F935ABADF5}.Debug|Win32.Build.0 = Debug|Win32 {4165A294-21F2-44CA-9B38-E3F935ABADF5}.Debug|x64.ActiveCfg = Debug|x64 @@ -37,22 +31,6 @@ Global {4165A294-21F2-44CA-9B38-E3F935ABADF5}.Release|Win32.Build.0 = Release|Win32 {4165A294-21F2-44CA-9B38-E3F935ABADF5}.Release|x64.ActiveCfg = Release|x64 {4165A294-21F2-44CA-9B38-E3F935ABADF5}.Release|x64.Build.0 = Release|x64 - {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|Win32.ActiveCfg = Debug|Win32 - {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|Win32.Build.0 = Debug|Win32 - {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|x64.ActiveCfg = Debug|x64 - {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|x64.Build.0 = Debug|x64 - {9F316E83-5AE5-4939-A723-305A94F48005}.Release|Win32.ActiveCfg = Release|Win32 - {9F316E83-5AE5-4939-A723-305A94F48005}.Release|Win32.Build.0 = Release|Win32 - {9F316E83-5AE5-4939-A723-305A94F48005}.Release|x64.ActiveCfg = Release|x64 - {9F316E83-5AE5-4939-A723-305A94F48005}.Release|x64.Build.0 = Release|x64 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|Win32.ActiveCfg = Debug|Win32 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|Win32.Build.0 = Debug|Win32 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|x64.ActiveCfg = Debug|x64 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|x64.Build.0 = Debug|x64 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|Win32.ActiveCfg = Release|Win32 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|Win32.Build.0 = Release|Win32 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|x64.ActiveCfg = Release|x64 - {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|x64.Build.0 = Release|x64 {345A953E-A004-4648-B442-DC5F9F11068C}.Debug|Win32.ActiveCfg = Debug|Win32 {345A953E-A004-4648-B442-DC5F9F11068C}.Debug|Win32.Build.0 = Debug|Win32 {345A953E-A004-4648-B442-DC5F9F11068C}.Debug|x64.ActiveCfg = Debug|x64 @@ -61,6 +39,38 @@ Global {345A953E-A004-4648-B442-DC5F9F11068C}.Release|Win32.Build.0 = Release|Win32 {345A953E-A004-4648-B442-DC5F9F11068C}.Release|x64.ActiveCfg = Release|x64 {345A953E-A004-4648-B442-DC5F9F11068C}.Release|x64.Build.0 = Release|x64 + {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|Win32.ActiveCfg = Debug|Win32 + {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|Win32.Build.0 = Debug|Win32 + {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|x64.ActiveCfg = Debug|x64 + {9F316E83-5AE5-4939-A723-305A94F48005}.Debug|x64.Build.0 = Debug|x64 + {9F316E83-5AE5-4939-A723-305A94F48005}.Release|Win32.ActiveCfg = Release|Win32 + {9F316E83-5AE5-4939-A723-305A94F48005}.Release|Win32.Build.0 = Release|Win32 + {9F316E83-5AE5-4939-A723-305A94F48005}.Release|x64.ActiveCfg = Release|x64 + {9F316E83-5AE5-4939-A723-305A94F48005}.Release|x64.Build.0 = Release|x64 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|Win32.ActiveCfg = Debug|Win32 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|Win32.Build.0 = Debug|Win32 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|x64.ActiveCfg = Debug|x64 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Debug|x64.Build.0 = Debug|x64 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|Win32.ActiveCfg = Release|Win32 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|Win32.Build.0 = Release|Win32 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|x64.ActiveCfg = Release|x64 + {9CDA7840-B7A5-496D-A527-E95571496D18}.Release|x64.Build.0 = Release|x64 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|Win32.ActiveCfg = Debug|Win32 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|Win32.Build.0 = Debug|Win32 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|x64.ActiveCfg = Debug|x64 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Debug|x64.Build.0 = Debug|x64 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|Win32.ActiveCfg = Release|Win32 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|Win32.Build.0 = Release|Win32 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|x64.ActiveCfg = Release|x64 + {4A1FB5EA-22F5-42A8-AB92-1D2DF5D47FB9}.Release|x64.Build.0 = Release|x64 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Debug|Win32.ActiveCfg = Debug|Win32 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Debug|Win32.Build.0 = Debug|Win32 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Debug|x64.ActiveCfg = Debug|x64 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Debug|x64.Build.0 = Debug|x64 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Release|Win32.ActiveCfg = Release|Win32 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Release|Win32.Build.0 = Release|Win32 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Release|x64.ActiveCfg = Release|x64 + {B4CF9797-519D-4AFE-A8F4-5141A6B521D3}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/examples/imgui_impl_dx12.cpp b/examples/imgui_impl_dx12.cpp index e9cd45c6..e55fdaf9 100644 --- a/examples/imgui_impl_dx12.cpp +++ b/examples/imgui_impl_dx12.cpp @@ -4,8 +4,10 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// Issues: -// [ ] 64-bit only for now! (Because sizeof(ImTextureId) == sizeof(void*)). See github.com/ocornut/imgui/pull/301 + +// Important: to compile on 32-bit systems, this back-end requires code to be compiled with '#define ImTextureID ImU64'. +// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. +// This define is done in the example .vcxproj file and need to be replicated in your app (by e.g. editing imconfig.h) // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. @@ -13,6 +15,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2020-09-08: DirectX12: Clarified support for building on 32-bit systems by redefining ImTextureID. // 2019-10-18: DirectX12: *BREAKING CHANGE* Added extra ID3D12DescriptorHeap parameter to ImGui_ImplDX12_Init() function. // 2019-05-29: DirectX12: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-04-30: DirectX12: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. diff --git a/examples/imgui_impl_dx12.h b/examples/imgui_impl_dx12.h index 52dab0ba..dd77ba8c 100644 --- a/examples/imgui_impl_dx12.h +++ b/examples/imgui_impl_dx12.h @@ -4,8 +4,10 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. -// Issues: -// [ ] 64-bit only for now! (Because sizeof(ImTextureId) == sizeof(void*)). See github.com/ocornut/imgui/pull/301 + +// Important: to compile on 32-bit systems, this back-end requires code to be compiled with '#define ImTextureID ImU64'. +// This is because we need ImTextureID to carry a 64-bit value and by default ImTextureID is defined as void*. +// This define is done in the example .vcxproj file and need to be replicated in your app (by e.g. editing imconfig.h) // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. diff --git a/imconfig.h b/imconfig.h index c6817de7..6b87dd6c 100644 --- a/imconfig.h +++ b/imconfig.h @@ -3,10 +3,11 @@ // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. //----------------------------------------------------------------------------- -// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/branch with your modifications to imconfig.h) -// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" -// If you do so you need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include -// the imgui*.cpp files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. +// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) +// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. +//----------------------------------------------------------------------------- +// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp +// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. //----------------------------------------------------------------------------- From e8447dea453fe11c4f7da6512b86f4e039f03261 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 8 Sep 2020 22:39:53 +0200 Subject: [PATCH 02/28] Backends: Vulkan: Removed unused shader code. Fix leaks. Avoid unnecessary pipeline creation for main viewport. Amend 41e2aa2. (#3459) --- examples/imgui_impl_vulkan.cpp | 31 ++++++++----------------------- examples/imgui_impl_vulkan.h | 2 +- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/examples/imgui_impl_vulkan.cpp b/examples/imgui_impl_vulkan.cpp index c47d5aab..c3135605 100644 --- a/examples/imgui_impl_vulkan.cpp +++ b/examples/imgui_impl_vulkan.cpp @@ -676,7 +676,7 @@ static void ImGui_ImplVulkan_CreatePipelineLayout(VkDevice device, const VkAlloc check_vk_result(err); } -static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline *pipeline) +static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationCallbacks* allocator, VkPipelineCache pipelineCache, VkRenderPass renderPass, VkSampleCountFlagBits MSAASamples, VkPipeline* pipeline) { ImGui_ImplVulkan_CreateShaderModules(device, allocator); @@ -784,24 +784,6 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkResult err; - VkShaderModule vert_module; - VkShaderModule frag_module; - - // Create The Shader Modules: - { - VkShaderModuleCreateInfo vert_info = {}; - vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - vert_info.codeSize = sizeof(__glsl_shader_vert_spv); - vert_info.pCode = (uint32_t*)__glsl_shader_vert_spv; - err = vkCreateShaderModule(v->Device, &vert_info, v->Allocator, &vert_module); - check_vk_result(err); - VkShaderModuleCreateInfo frag_info = {}; - frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - frag_info.codeSize = sizeof(__glsl_shader_frag_spv); - frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; - err = vkCreateShaderModule(v->Device, &frag_info, v->Allocator, &frag_module); - check_vk_result(err); - } if (!g_FontSampler) { @@ -867,9 +849,6 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, g_RenderPass, v->MSAASamples, &g_Pipeline); - vkDestroyShaderModule(v->Device, vert_module, v->Allocator); - vkDestroyShaderModule(v->Device, frag_module, v->Allocator); - return true; } @@ -894,6 +873,8 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); ImGui_ImplVulkan_DestroyFontUploadObjects(); + if (g_ShaderModuleVert) { vkDestroyShaderModule(v->Device, g_ShaderModuleVert, v->Allocator); g_ShaderModuleVert = VK_NULL_HANDLE; } + if (g_ShaderModuleFrag) { vkDestroyShaderModule(v->Device, g_ShaderModuleFrag, v->Allocator); g_ShaderModuleFrag = VK_NULL_HANDLE; } if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } @@ -1210,7 +1191,10 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V info.pDependencies = &dependency; err = vkCreateRenderPass(device, &info, allocator, &wd->RenderPass); check_vk_result(err); - ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline); + + // We do not create a pipeline by default as this is also used by examples' main.cpp, + // but secondary viewport in multi-viewport mode may want to create one with: + //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline); } // Create The Image Views @@ -1277,6 +1261,7 @@ void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui IM_FREE(wd->FrameSemaphores); wd->Frames = NULL; wd->FrameSemaphores = NULL; + vkDestroyPipeline(device, wd->Pipeline, allocator); vkDestroyRenderPass(device, wd->RenderPass, allocator); vkDestroySwapchainKHR(device, wd->Swapchain, allocator); vkDestroySurfaceKHR(instance, wd->Surface, allocator); diff --git a/examples/imgui_impl_vulkan.h b/examples/imgui_impl_vulkan.h index 951574cd..d5d2516a 100644 --- a/examples/imgui_impl_vulkan.h +++ b/examples/imgui_impl_vulkan.h @@ -108,7 +108,7 @@ struct ImGui_ImplVulkanH_Window VkSurfaceFormatKHR SurfaceFormat; VkPresentModeKHR PresentMode; VkRenderPass RenderPass; - VkPipeline Pipeline; // The window pipeline uses a different VkRenderPass than the user's + VkPipeline Pipeline; // The window pipeline may uses a different VkRenderPass than the one passed in ImGui_ImplVulkan_InitInfo bool ClearEnable; VkClearValue ClearValue; uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount) From a8f409a84895fc7ab291ea7ca3eec555aa47cc37 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 16 Sep 2020 10:28:58 +0200 Subject: [PATCH 03/28] Examples: DX12: Enable breaking on any warning/error when debug interface is enabled. (#3462, #3472) + misc comments & minor fixes. --- docs/CHANGELOG.txt | 1 + examples/example_win32_directx12/main.cpp | 19 ++++++++++++++++--- imgui.h | 8 +++++--- imgui_demo.cpp | 11 +++++------ imgui_draw.cpp | 4 ++-- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 092bd005..0ad6048e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -73,6 +73,7 @@ Other Changes: - Examples: Vulkan: Reworked buffer resize handling, fix for Linux/X11. (#3390, #2626) [@RoryO] - Examples: Vulkan: Switch validation layer to use "VK_LAYER_KHRONOS_validation" instead of "VK_LAYER_LUNARG_standard_validation" which is deprecated (#3459) [@FunMiles] +- Examples: DX12: Enable breaking on any warning/error when debug interface is enabled. - Examples: DX12: Added '#define ImTextureID ImU64' in project and build files to also allow building on 32-bit systems. Added project to default Visual Studio solution file. (#301) diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 4a263916..a2cd494a 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -241,19 +241,32 @@ bool CreateDeviceD3D(HWND hWnd) sd.Stereo = FALSE; } + // [DEBUG] Enable debug interface #ifdef DX12_ENABLE_DEBUG_LAYER ID3D12Debug* pdx12Debug = NULL; if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&pdx12Debug)))) - { pdx12Debug->EnableDebugLayer(); - pdx12Debug->Release(); - } #endif + // Create device D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; if (D3D12CreateDevice(NULL, featureLevel, IID_PPV_ARGS(&g_pd3dDevice)) != S_OK) return false; + // [DEBUG] Setup debug interface to break on any warnings/errors +#ifdef DX12_ENABLE_DEBUG_LAYER + if (pdx12Debug != NULL) + { + ID3D12InfoQueue* pInfoQueue = NULL; + g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&pInfoQueue)); + pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true); + pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true); + pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); + pInfoQueue->Release(); + pdx12Debug->Release(); + } +#endif + { D3D12_DESCRIPTOR_HEAP_DESC desc = {}; desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; diff --git a/imgui.h b/imgui.h index 98a65699..d59bef30 100644 --- a/imgui.h +++ b/imgui.h @@ -887,13 +887,15 @@ enum ImGuiTreeNodeFlags_ // small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags. // It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags. // - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0. +// IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter +// and want to another another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag. // - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later). enum ImGuiPopupFlags_ { ImGuiPopupFlags_None = 0, - ImGuiPopupFlags_MouseButtonLeft = 0, // For BeginPopupContext*(): open on Left Mouse release. Guaranted to always be == 0 (same as ImGuiMouseButton_Left) - ImGuiPopupFlags_MouseButtonRight = 1, // For BeginPopupContext*(): open on Right Mouse release. Guaranted to always be == 1 (same as ImGuiMouseButton_Right) - ImGuiPopupFlags_MouseButtonMiddle = 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranted to always be == 2 (same as ImGuiMouseButton_Middle) + ImGuiPopupFlags_MouseButtonLeft = 0, // For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left) + ImGuiPopupFlags_MouseButtonRight = 1, // For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right) + ImGuiPopupFlags_MouseButtonMiddle = 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle) ImGuiPopupFlags_MouseButtonMask_ = 0x1F, ImGuiPopupFlags_MouseButtonDefault_ = 1, ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c7ab4310..f6bc7de4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -854,7 +854,7 @@ static void ShowDemoWindowWidgets() ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); if (n == 0) ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); - if (n == 1) + else ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); // Draw actual text bounding box, following by marker of our expected limit (should not overlap!) @@ -953,7 +953,7 @@ static void ShowDemoWindowWidgets() int frame_padding = -1 + i; // -1 == uses default padding (style.FramePadding) ImVec2 size = ImVec2(32.0f, 32.0f); // Size of the image we want to make visible ImVec2 uv0 = ImVec2(0.0f, 0.0f); // UV coordinates for lower-left - ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32 / my_tex_h); // UV coordinates for (32,32) in our texture + ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h);// UV coordinates for (32,32) in our texture ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Black background ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint if (ImGui::ImageButton(my_tex_id, size, uv0, uv1, frame_padding, bg_col, tint_col)) @@ -982,7 +982,7 @@ static void ShowDemoWindowWidgets() // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; static int item_current_idx = 0; // Here our selection data is an index. - const char* combo_label = items[item_current_idx]; // Label to preview before opening the combo (technically could be anything)( + const char* combo_label = items[item_current_idx]; // Label to preview before opening the combo (technically it could be anything) if (ImGui::BeginCombo("combo 1", combo_label, flags)) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) @@ -4939,16 +4939,15 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) const float DISTANCE = 10.0f; static int corner = 0; ImGuiIO& io = ImGui::GetIO(); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; if (corner != -1) { + window_flags |= ImGuiWindowFlags_NoMove; ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE); ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); } ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; - if (corner != -1) - window_flags |= ImGuiWindowFlags_NoMove; if (ImGui::Begin("Example: Simple overlay", p_open, window_flags)) { ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2f87366a..80d8d617 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -339,7 +339,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) } //----------------------------------------------------------------------------- -// ImDrawList +// [SECTION] ImDrawList //----------------------------------------------------------------------------- ImDrawListSharedData::ImDrawListSharedData() @@ -1405,7 +1405,7 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi //----------------------------------------------------------------------------- -// ImDrawListSplitter +// [SECTION] ImDrawListSplitter //----------------------------------------------------------------------------- // FIXME: This may be a little confusing, trying to be a little too low-level/optimal instead of just doing vector swap.. //----------------------------------------------------------------------------- From a1597cff088a827e98688632f6523fae2b6513af Mon Sep 17 00:00:00 2001 From: Pierre-Loup Pagniez Date: Tue, 15 Sep 2020 12:39:55 +0200 Subject: [PATCH 04/28] Backends: DX12: Fix D3D12 Debug Layer warning if scissor rect is 0 width or 0 height. (#3472, #3462) In the event where the scissor rect is 0 width or 0 height, don't call Draw, as it generates warnings if the D3D12 Debug Layer is enabled, and nothing would have been drawn anyway. --- docs/CHANGELOG.txt | 1 + examples/imgui_impl_dx12.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 0ad6048e..3f659f8e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -70,6 +70,7 @@ Other Changes: own render pass. (#3455, #3459) [@FunMiles] - Backends: DX12: Clarified that imgui_impl_dx12 can be compiled on 32-bit systems by redefining the ImTextureID to be 64-bit (e.g. '#define ImTextureID ImU64' in imconfig.h). (#301) +- Backends: DX12: Fix debug layer warning when scissor rect is zero-sized. (#3472, #3462) [@StoneWolf] - Examples: Vulkan: Reworked buffer resize handling, fix for Linux/X11. (#3390, #2626) [@RoryO] - Examples: Vulkan: Switch validation layer to use "VK_LAYER_KHRONOS_validation" instead of "VK_LAYER_LUNARG_standard_validation" which is deprecated (#3459) [@FunMiles] diff --git a/examples/imgui_impl_dx12.cpp b/examples/imgui_impl_dx12.cpp index e55fdaf9..c33ca64f 100644 --- a/examples/imgui_impl_dx12.cpp +++ b/examples/imgui_impl_dx12.cpp @@ -235,9 +235,12 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL { // Apply Scissor, Bind texture, Draw const D3D12_RECT r = { (LONG)(pcmd->ClipRect.x - clip_off.x), (LONG)(pcmd->ClipRect.y - clip_off.y), (LONG)(pcmd->ClipRect.z - clip_off.x), (LONG)(pcmd->ClipRect.w - clip_off.y) }; - ctx->SetGraphicsRootDescriptorTable(1, *(D3D12_GPU_DESCRIPTOR_HANDLE*)&pcmd->TextureId); - ctx->RSSetScissorRects(1, &r); - ctx->DrawIndexedInstanced(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); + if (r.right > r.left && r.bottom > r.top) + { + ctx->SetGraphicsRootDescriptorTable(1, *(D3D12_GPU_DESCRIPTOR_HANDLE*)&pcmd->TextureId); + ctx->RSSetScissorRects(1, &r); + ctx->DrawIndexedInstanced(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); + } } } global_idx_offset += cmd_list->IdxBuffer.Size; From d2939ce0a11116a63b942ca4b02ba33cbf3635a7 Mon Sep 17 00:00:00 2001 From: Bartosz Szreder Date: Wed, 16 Sep 2020 15:17:45 +0200 Subject: [PATCH 05/28] Columns: Make sure the ClipRect is valid. (#3475) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3f659f8e..922e30b3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,8 @@ Other Changes: - Tab Bar: Keep tab item close button visible while dragging a tab (independent of hovering state). - Tab Bar: Fixed a small bug where toggling a tab bar from Reorderable to not Reorderable would leave tabs reordered in the tab list popup. [@Xipiryon] +- Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of + a fully clipped column. (#3475) [@szreder] - Metrics: Various tweaks, listing windows front-to-back, greying inactive items when possible. - Demo: Add simple InputText() callbacks demo (aside from the more elaborate ones in 'Examples->Console'). - Backends: Vulkan: Some internal refactor aimed at allowing multi-viewport feature to create their diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c797b1a0..d6bf2811 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7938,7 +7938,7 @@ void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlag float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n)); float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f); column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); - column->ClipRect.ClipWith(window->ClipRect); + column->ClipRect.ClipWithFull(window->ClipRect); } if (columns->Count > 1) From 645a6e03425a1462d71d81b04b5186b225af01a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 16 Sep 2020 18:36:42 +0200 Subject: [PATCH 06/28] Bypass unnecessary formatting when using the TextColored()/TextWrapped()/TextDisabled() helpers with a "%s" format string. (#3466) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 922e30b3..50af7875 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,8 @@ Other Changes: where v_min == v_max. (#3361) - SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both with signed and unsigned types. Added reverse Sliders to Demo. (#3432, #3449) [@rokups] +- Text: Bypass unnecessary formatting when using the TextColored()/TextWrapped()/TextDisabled() helpers + with a "%s" format string. (#3466) - BeginMenuBar: Fixed minor bug where CursorPosMax gets pushed to CursorPos prior to calling BeginMenuBar(), so e.g. calling the function at the end of a window would often add +ItemSpacing.y to scrolling range. - TreeNode, CollapsingHeader: Made clicking on arrow toggle toggle the open state on the Mouse Down event diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d6bf2811..6894f257 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -274,7 +274,10 @@ void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) { PushStyleColor(ImGuiCol_Text, col); - TextV(fmt, args); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + TextV(fmt, args); PopStyleColor(); } @@ -288,8 +291,12 @@ void ImGui::TextDisabled(const char* fmt, ...) void ImGui::TextDisabledV(const char* fmt, va_list args) { - PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); - TextV(fmt, args); + ImGuiContext& g = *GImGui; + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + TextV(fmt, args); PopStyleColor(); } @@ -303,11 +310,14 @@ void ImGui::TextWrapped(const char* fmt, ...) void ImGui::TextWrappedV(const char* fmt, va_list args) { - ImGuiWindow* window = GetCurrentWindow(); - bool need_backup = (window->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set + ImGuiContext& g = *GImGui; + bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set if (need_backup) PushTextWrapPos(0.0f); - TextV(fmt, args); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + TextV(fmt, args); if (need_backup) PopTextWrapPos(); } From 2460f2abe3eed4e81b37eee5b4cabf920f05df16 Mon Sep 17 00:00:00 2001 From: Julian Webb Date: Thu, 10 Sep 2020 13:02:45 -0500 Subject: [PATCH 07/28] Backends: OpenGL3: Fix to avoid calling glBindSampler() with version <= 3.2 (#3467, #1985) (nb: GLEW sets the define we previously used) --- examples/imgui_impl_opengl3.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/imgui_impl_opengl3.cpp b/examples/imgui_impl_opengl3.cpp index 21e8852b..a24de1ff 100644 --- a/examples/imgui_impl_opengl3.cpp +++ b/examples/imgui_impl_opengl3.cpp @@ -263,10 +263,12 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid glUseProgram(g_ShaderHandle); glUniform1i(g_AttribLocationTex, 0); glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); -#ifdef GL_SAMPLER_BINDING - glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. + +#if (!defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)) + if(g_GlVersion > 320) + glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. #endif - + (void)vertex_array_object; #ifndef IMGUI_IMPL_OPENGL_ES2 glBindVertexArray(vertex_array_object); @@ -299,8 +301,10 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) glActiveTexture(GL_TEXTURE0); GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program); GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture); -#ifdef GL_SAMPLER_BINDING - GLuint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); +#if (!defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)) + GLuint last_sampler; + if(g_GlVersion > 320) + glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); #endif GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 @@ -391,8 +395,9 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Restore modified GL state glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); -#ifdef GL_SAMPLER_BINDING - glBindSampler(0, last_sampler); +#if (!defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)) + if(g_GlVersion > 320) + glBindSampler(0, last_sampler); #endif glActiveTexture(last_active_texture); #ifndef IMGUI_IMPL_OPENGL_ES2 From 825f699bde188a5c471e0c424009699165d39a3d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2020 09:33:48 +0200 Subject: [PATCH 08/28] Backends: OpenGL3: Amends (#3467, #1985) --- docs/CHANGELOG.txt | 2 ++ examples/imgui_impl_dx12.cpp | 1 + examples/imgui_impl_opengl3.cpp | 31 ++++++++++++++++--------------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 50af7875..7df46943 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -70,6 +70,8 @@ Other Changes: a fully clipped column. (#3475) [@szreder] - Metrics: Various tweaks, listing windows front-to-back, greying inactive items when possible. - Demo: Add simple InputText() callbacks demo (aside from the more elaborate ones in 'Examples->Console'). +- Backends: OpenGL3: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 contexts which have + the defines set by a loader. (#3467, #1985) [@jjwebb] - Backends: Vulkan: Some internal refactor aimed at allowing multi-viewport feature to create their own render pass. (#3455, #3459) [@FunMiles] - Backends: DX12: Clarified that imgui_impl_dx12 can be compiled on 32-bit systems by redefining diff --git a/examples/imgui_impl_dx12.cpp b/examples/imgui_impl_dx12.cpp index c33ca64f..8d3ee417 100644 --- a/examples/imgui_impl_dx12.cpp +++ b/examples/imgui_impl_dx12.cpp @@ -15,6 +15,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2020-09-16: DirectX12: Avoid rendering calls with zero-sized scissor rectangle since it generates a validation layer warning. // 2020-09-08: DirectX12: Clarified support for building on 32-bit systems by redefining ImTextureID. // 2019-10-18: DirectX12: *BREAKING CHANGE* Added extra ID3D12DescriptorHeap parameter to ImGui_ImplDX12_Init() function. // 2019-05-29: DirectX12: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. diff --git a/examples/imgui_impl_opengl3.cpp b/examples/imgui_impl_opengl3.cpp index a24de1ff..8a1f5873 100644 --- a/examples/imgui_impl_opengl3.cpp +++ b/examples/imgui_impl_opengl3.cpp @@ -13,6 +13,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2020-09-17: OpenGL: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 context which have the defines set by a loader. // 2020-07-10: OpenGL: Added support for glad2 OpenGL loader. // 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX. // 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by inverting projection matrix. @@ -81,7 +82,6 @@ #include // intptr_t #endif - // GL includes #if defined(IMGUI_IMPL_OPENGL_ES2) #include @@ -124,10 +124,13 @@ using namespace gl; #endif // Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have. -#if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) || !defined(GL_VERSION_3_2) -#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 0 -#else -#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET 1 +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#endif + +// Desktop GL 3.3+ has glBindSampler() +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && defined(GL_VERSION_3_3) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER #endif // OpenGL Data @@ -155,7 +158,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) // Setup back-end capabilities flags ImGuiIO& io = ImGui::GetIO(); io.BackendRendererName = "imgui_impl_opengl3"; -#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (g_GlVersion >= 320) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. #endif @@ -264,8 +267,8 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid glUniform1i(g_AttribLocationTex, 0); glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); -#if (!defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)) - if(g_GlVersion > 320) +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (g_GlVersion >= 330) glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. #endif @@ -301,10 +304,8 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) glActiveTexture(GL_TEXTURE0); GLuint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program); GLuint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture); -#if (!defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)) - GLuint last_sampler; - if(g_GlVersion > 320) - glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + GLuint last_sampler; if (g_GlVersion >= 330) { glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); } else { last_sampler = 0; } #endif GLuint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer); #ifndef IMGUI_IMPL_OPENGL_ES2 @@ -376,7 +377,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Bind texture, Draw glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); -#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (g_GlVersion >= 320) glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); else @@ -395,8 +396,8 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Restore modified GL state glUseProgram(last_program); glBindTexture(GL_TEXTURE_2D, last_texture); -#if (!defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)) - if(g_GlVersion > 320) +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (g_GlVersion >= 330) glBindSampler(0, last_sampler); #endif glActiveTexture(last_active_texture); From b7b08f52a4b9c08bbcacb55209139cc9e58dc732 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Wed, 13 May 2020 14:56:13 +0300 Subject: [PATCH 09/28] Fix popup and tooltip positioning when not fitting in the screen. --- imgui.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index ecb3d7be..7eec4e08 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8016,11 +8016,22 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s continue; float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); - if (avail_w < size.x || avail_h < size.y) + + // There is no point in switching left/right sides when popup height exceeds available height or top/bottom + // sides when popup width exceeds available width. + if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down)) continue; + else if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right)) + continue; + ImVec2 pos; pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + + // Clamp top-left (or top-right for RTL languages in the future) corner of popup to remain visible. + pos.x = ImMax(pos.x, r_outer.Min.x); + pos.y = ImMax(pos.y, r_outer.Min.y); + *last_dir = dir; return pos; } From c47bcb25edbe05e694d83910d6e4c719d1fec7b9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2020 11:00:56 +0200 Subject: [PATCH 10/28] Fix popup and tooltip positioning when not fitting in the screen. Amend fa42ccea8. # Conflicts: # docs/CHANGELOG.txt --- docs/CHANGELOG.txt | 2 ++ docs/TODO.txt | 1 - imgui.cpp | 65 +++++++++++++++++++++++++--------------------- imgui_internal.h | 5 ++-- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 7df46943..f27ed543 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -68,6 +68,8 @@ Other Changes: tabs reordered in the tab list popup. [@Xipiryon] - Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of a fully clipped column. (#3475) [@szreder] +- Popups, Tooltips: Fix edge cases issues with positionning popups and tooltips when they are larger than + viewport on either or both axises. [@Rokups] - Metrics: Various tweaks, listing windows front-to-back, greying inactive items when possible. - Demo: Add simple InputText() callbacks demo (aside from the more elaborate ones in 'Examples->Console'). - Backends: OpenGL3: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 contexts which have diff --git a/docs/TODO.txt b/docs/TODO.txt index 509fcca5..9f99871f 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -219,7 +219,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - popups/modals: although it is sometimes convenient that popups/modals lifetime is owned by imgui, we could also a bool-owned-by-user api as long as Begin() return value testing is enforced. - tooltip: drag and drop with tooltip near monitor edges lose/changes its last direction instead of locking one. The drag and drop tooltip should always follow without changing direction. - - tooltip: tooltip that doesn't fit in entire screen seems to lose their "last preferred direction" and may teleport when moving mouse. - tooltip: allow to set the width of a tooltip to allow TextWrapped() etc. while keeping the height automatic. - tooltip: tooltips with delay timers? or general timer policy? (instantaneous vs timed): IsItemHovered() with timer + implicit aabb-id for items with no ID. (#1485) - tooltip: drag tooltip hovering over source widget with IsItemHovered/SetTooltip flickers. diff --git a/imgui.cpp b/imgui.cpp index 7eec4e08..700e7e4e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8007,37 +8007,47 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s } } - // Default popup policy - const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; - for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) + // Tooltip and Default popup policy + // (Always first try the direction we used on the last frame, if any) + if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default) { - const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; - if (n != -1 && dir == *last_dir) // Already tried this direction? - continue; - float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); - float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) + { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; - // There is no point in switching left/right sides when popup height exceeds available height or top/bottom - // sides when popup width exceeds available width. - if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down)) - continue; - else if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right)) - continue; + const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); + const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); - ImVec2 pos; - pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; - pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + // If there not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width) + if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right)) + continue; + if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down)) + continue; - // Clamp top-left (or top-right for RTL languages in the future) corner of popup to remain visible. - pos.x = ImMax(pos.x, r_outer.Min.x); - pos.y = ImMax(pos.y, r_outer.Min.y); + ImVec2 pos; + pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; + pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; - *last_dir = dir; - return pos; + // Clamp top-left corner of popup + pos.x = ImMax(pos.x, r_outer.Min.x); + pos.y = ImMax(pos.y, r_outer.Min.y); + + *last_dir = dir; + return pos; + } } - // Fallback, try to keep within display + // Fallback when not enough room: *last_dir = ImGuiDir_None; + + // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. + if (policy == ImGuiPopupPositionPolicy_Tooltip) + return ref_pos + ImVec2(2, 2); + + // Otherwise try to keep within display ImVec2 pos = ref_pos; pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); @@ -8070,12 +8080,12 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) r_avoid = ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX, parent_window->ClipRect.Max.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field else r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); - return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); } if (window->Flags & ImGuiWindowFlags_Popup) { ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1); - return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); } if (window->Flags & ImGuiWindowFlags_Tooltip) { @@ -8087,10 +8097,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); else r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. - ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); - if (window->AutoPosLastDirection == ImGuiDir_None) - pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. - return pos; + return FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); } IM_ASSERT(0); return window->Pos; diff --git a/imgui_internal.h b/imgui_internal.h index aceeebca..ec37b38b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -777,7 +777,8 @@ enum ImGuiNavLayer enum ImGuiPopupPositionPolicy { ImGuiPopupPositionPolicy_Default, - ImGuiPopupPositionPolicy_ComboBox + ImGuiPopupPositionPolicy_ComboBox, + ImGuiPopupPositionPolicy_Tooltip }; struct ImGuiDataTypeTempStorage @@ -1879,7 +1880,7 @@ namespace ImGui IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags); IMGUI_API ImGuiWindow* GetTopMostPopupModal(); IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); - IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default); + IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); // Gamepad/Keyboard Navigation IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); From fbf70070bba5e582c14f4cbe1953f0dab29e78f1 Mon Sep 17 00:00:00 2001 From: Louis Schnellbach Date: Thu, 17 Sep 2020 11:32:56 +0200 Subject: [PATCH 11/28] InputText: Fixed minor inconsistency when pressing Down on the last line when it doesn't have a carriage return (it used to move to the end of the line) + fixed two of our typos in stb_textedit.h --- docs/CHANGELOG.txt | 4 +++- imstb_textedit.h | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f27ed543..6e08e3af 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -50,7 +50,9 @@ Other Changes: It is a rather unusual or useless combination of features but no reason it shouldn't work! - InputText: Fixed minor scrolling glitch when erasing trailing lines in InputTextMultiline(). - InputText: Fixed cursor being partially covered after using Ctrl+End key. -- InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454). +- InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454) +- InputText: Fixed minor inconsistency when pressing Down on the last line when it doesn't have a carriage + return (it used to move to the end of the line). [@Xipiryon] - DragFloat, DragScalar: Fixed ImGuiSliderFlags_ClampOnInput not being honored in the special case where v_min == v_max. (#3361) - SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both diff --git a/imstb_textedit.h b/imstb_textedit.h index 2077d02a..e7205e77 100644 --- a/imstb_textedit.h +++ b/imstb_textedit.h @@ -177,7 +177,7 @@ // Keyboard input must be encoded as a single integer value; e.g. a character code // and some bitflags that represent shift states. to simplify the interface, SHIFT must // be a bitflag, so we can test the shifted state of cursor movements to allow selection, -// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. // // You can encode other things, such as CONTROL or ALT, in additional bits, and // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, @@ -877,6 +877,12 @@ retry: // now find character position down a row if (find.length) { + + // [DEAR IMGUI] + // going down while being on the last line shouldn't bring us to that line end + if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) + break; + float goal_x = state->has_preferred_x ? state->preferred_x : find.x; float x; int start = find.first_char + find.length; @@ -1134,7 +1140,7 @@ static void stb_textedit_discard_redo(StbUndoState *state) state->undo_rec[i].char_storage += n; } // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' - // {DEAR IMGUI] + // [DEAR IMGUI] size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; From c206a193737811193a0b48ef2d35fe028fa0996e Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 17 Sep 2020 16:43:23 +0200 Subject: [PATCH 12/28] Removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. (#1619) + Fonts: AddFontDefault() adjust its vertical offset based on floor(size/13) instead of always +1. --- docs/CHANGELOG.txt | 11 ++++++++++- docs/FONTS.md | 11 ++--------- docs/README.md | 2 +- imgui.cpp | 1 + imgui.h | 3 +-- imgui_demo.cpp | 5 ++--- imgui_draw.cpp | 11 +++++------ imgui_widgets.cpp | 1 - 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6e08e3af..94a069f7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,6 +35,14 @@ HOW TO UPDATE? VERSION 1.79 WIP (In Progress) ----------------------------------------------------------------------- +Breaking Changes: + +- Fonts: Removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied + after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. + It was also getting in the way of better font scaling, so let's get rid of it now! + If you used DisplayOffset it was probably in association to rasterizing a font at a specific size, + in which case the corresponding offset may be reported into GlyphOffset. (#1619) + Other Changes: - Window: Fixed using non-zero pivot in SetNextWindowPos() when the window is collapsed. (#3433) @@ -72,6 +80,7 @@ Other Changes: a fully clipped column. (#3475) [@szreder] - Popups, Tooltips: Fix edge cases issues with positionning popups and tooltips when they are larger than viewport on either or both axises. [@Rokups] +- Fonts: AddFontDefault() adjust its vertical offset based on floor(size/13) instead of always +1. - Metrics: Various tweaks, listing windows front-to-back, greying inactive items when possible. - Demo: Add simple InputText() callbacks demo (aside from the more elaborate ones in 'Examples->Console'). - Backends: OpenGL3: Fix to avoid compiling/calling glBindSampler() on ES or pre 3.3 contexts which have @@ -1481,7 +1490,7 @@ Breaking Changes: - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. - Renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums. - Fonts: Moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. -- Fonts: changed ImFont::DisplayOffset.y to defaults to 0 instead of +1. Fixed vertical rounding of Ascent/Descent to match TrueType renderer. +- Fonts: Changed ImFont::DisplayOffset.y to defaults to 0 instead of +1. Fixed vertical rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting (not assigning) to ImFont::DisplayOffset check if your fonts are correctly aligned vertically. (#1619) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment. - Obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). diff --git a/docs/FONTS.md b/docs/FONTS.md index 158e08ab..c2312fd5 100644 --- a/docs/FONTS.md +++ b/docs/FONTS.md @@ -142,13 +142,6 @@ ImGui::SliderFloat("float", &f, 0.0f, 1.0f); ![sample code output](https://raw.githubusercontent.com/wiki/ocornut/imgui/web/v160/code_sample_02_jp.png)
_(settings: Dark style (left), Light style (right) / Font: NotoSansCJKjp-Medium, 20px / Rounding: 5)_ - -**Offset font vertically by altering the `io.Font->DisplayOffset` value:** -```cpp -ImFont* font = io.Fonts->AddFontFromFileTTF("font.ttf", size_pixels); -font->DisplayOffset.y = 1; // Render 1 pixel down -``` - **Font Atlas too large?** - If you have very large number of glyphs or multiple fonts, the texture may become too big for your graphics API. The typical result of failing to upload a texture is if every glyphs appears as white rectangles. @@ -337,13 +330,13 @@ DroidSans.ttf ProggyClean.ttf Copyright (c) 2004, 2005 Tristan Grimmer MIT License - recommended loading setting: Size = 13.0, DisplayOffset.Y = +1 + recommended loading setting: Size = 13.0, GlyphOffset.y = +1 http://www.proggyfonts.net/ ProggyTiny.ttf Copyright (c) 2004, 2005 Tristan Grimmer MIT License - recommended loading setting: Size = 10.0, DisplayOffset.Y = +1 + recommended loading setting: Size = 10.0, GlyphOffset.y = +1 http://www.proggyfonts.net/ Karla-Regular.ttf diff --git a/docs/README.md b/docs/README.md index 230aa03c..41869257 100644 --- a/docs/README.md +++ b/docs/README.md @@ -114,7 +114,7 @@ Officially maintained bindings (in repository): - Frameworks: Emscripten, Allegro5, Marmalade. Third-party bindings (see [Bindings](https://github.com/ocornut/imgui/wiki/Bindings/) page): -- Languages: C, C# and: Beef, ChaiScript, D, Go, Haskell, Haxe/hxcpp, Java, JavaScript, Julia, Kotlin, Lua, Odin, Pascal, PureBasic, Python, Ruby, Rust, Swift... +- Languages: C, C# and: Beef, ChaiScript, Crystal, D, Go, Haskell, Haxe/hxcpp, Java, JavaScript, Julia, Kotlin, Lua, Odin, Pascal, PureBasic, Python, Ruby, Rust, Swift... - Frameworks: AGS/Adventure Game Studio, Amethyst, bsf, Cinder, Cocos2d-x, Diligent Engine, Flexium, GML/Game Maker Studio2, Godot, GTK3+OpenGL3, Irrlicht Engine, LÖVE+LUA, Magnum, NanoRT, nCine, Nim Game Lib, Ogre, openFrameworks, OSG/OpenSceneGraph, Orx, Photoshop, px_render, Qt/QtDirect3D, SFML, Sokol, Unity, Unreal Engine 4, vtk, Win32 GDI, WxWidgets. - Note that C bindings ([cimgui](https://github.com/cimgui/cimgui)) are auto-generated, you can use its json/lua output to generate bindings for other languages. diff --git a/imgui.cpp b/imgui.cpp index 700e7e4e..a3c7e72a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -372,6 +372,7 @@ CODE When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. It was also getting in the way of better font scaling, so let's get rid of it now! - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). worked out a backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those functions: diff --git a/imgui.h b/imgui.h index d59bef30..48889186 100644 --- a/imgui.h +++ b/imgui.h @@ -2406,11 +2406,10 @@ struct ImFont float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX float FontSize; // 4 // in // // Height of characters/line, set during loading (don't change after loading) - // Members: Hot ~36/48 bytes (for CalcTextSize + render loop) + // Members: Hot ~28/40 bytes (for CalcTextSize + render loop) ImVector IndexLookup; // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // // All glyphs. const ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) - ImVec2 DisplayOffset; // 8 // in // = (0,0) // Offset font rendering by xx pixels // Members: Cold ~32/40 bytes ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f6bc7de4..9099116c 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3715,7 +3715,6 @@ static void NodeFont(ImFont* font) "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, "%.0f"); ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); ImGui::Text("Fallback character: '%c' (U+%04X)", font->FallbackChar, font->FallbackChar); ImGui::Text("Ellipsis character: '%c' (U+%04X)", font->EllipsisChar, font->EllipsisChar); @@ -3724,8 +3723,8 @@ static void NodeFont(ImFont* font) for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) if (font->ConfigData) if (const ImFontConfig* cfg = &font->ConfigData[config_i]) - ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", - config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); + ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { // Display all glyphs of the fonts in separate pages of 256 characters diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 80d8d617..3f666698 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1891,11 +1891,11 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) if (font_cfg.Name[0] == '\0') ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels); font_cfg.EllipsisChar = (ImWchar)0x0085; + font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); - font->DisplayOffset.y = 1.0f; return font; } @@ -2768,7 +2768,6 @@ ImFont::ImFont() FallbackAdvanceX = 0.0f; FallbackChar = (ImWchar)'?'; EllipsisChar = (ImWchar)-1; - DisplayOffset = ImVec2(0.0f, 0.0f); FallbackGlyph = NULL; ContainerAtlas = NULL; ConfigData = NULL; @@ -3163,8 +3162,8 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col if (!glyph || !glyph->Visible) return; float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; - pos.x = IM_FLOOR(pos.x + DisplayOffset.x); - pos.y = IM_FLOOR(pos.y + DisplayOffset.y); + pos.x = IM_FLOOR(pos.x); + pos.y = IM_FLOOR(pos.y); draw_list->PrimReserve(6, 4); draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); } @@ -3175,8 +3174,8 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. // Align to be pixel perfect - pos.x = IM_FLOOR(pos.x + DisplayOffset.x); - pos.y = IM_FLOOR(pos.y + DisplayOffset.y); + pos.x = IM_FLOOR(pos.x); + pos.y = IM_FLOOR(pos.y); float x = pos.x; float y = pos.y; if (y > clip_rect.w) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6894f257..466bc803 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3958,7 +3958,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImFont* password_font = &g.InputTextPasswordFont; password_font->FontSize = g.Font->FontSize; password_font->Scale = g.Font->Scale; - password_font->DisplayOffset = g.Font->DisplayOffset; password_font->Ascent = g.Font->Ascent; password_font->Descent = g.Font->Descent; password_font->ContainerAtlas = g.Font->ContainerAtlas; From 8eca736a7a0e42acebc8a1e497c319a351152659 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 18 Sep 2020 10:03:14 +0200 Subject: [PATCH 13/28] Update binary link (contents of 20200412.zip's dx11.exe is flagged by Windows Defender, can't currently repro) --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 41869257..41279f60 100644 --- a/docs/README.md +++ b/docs/README.md @@ -98,7 +98,7 @@ Calling the `ImGui::ShowDemoWindow()` function will create a demo window showcas ![screenshot demo](https://raw.githubusercontent.com/wiki/ocornut/imgui/web/v167/v167-misc.png) You should be able to build the examples from sources (tested on Windows/Mac/Linux). If you don't, let us know! If you want to have a quick look at some Dear ImGui features, you can download Windows binaries of the demo app here: -- [imgui-demo-binaries-20200412.zip](http://www.dearimgui.org/binaries/imgui-demo-binaries-20200412.zip) (Windows, 1.76, built 2020/04/12, master branch) or [older demo binaries](http://www.dearimgui.org/binaries). +- [imgui-demo-binaries-20200918.zip](https://www.dearimgui.org/binaries/imgui-demo-binaries-20200918.zip) (Windows, 1.78 WIP, built 2020/09/18, master branch) or [older demo binaries](https://www.dearimgui.org/binaries). The demo applications are not DPI aware so expect some blurriness on a 4K screen. For DPI awareness in your application, you can load/reload your font at different scale, and scale your style with `style.ScaleAllSizes()` (see [FAQ](https://www.dearimgui.org/faq)). From ec945f44b5eff1d82129233be5643abbff2845da Mon Sep 17 00:00:00 2001 From: Louis Schnellbach Date: Thu, 17 Sep 2020 11:39:54 +0200 Subject: [PATCH 14/28] InputText: Added support for Page Up/Down in InputTextMultiline. (#3430) + fix stb_textedit.h to build with C language (amend fbf70070) --- docs/CHANGELOG.txt | 6 ++-- imgui_widgets.cpp | 14 +++++++-- imstb_textedit.h | 72 ++++++++++++++++++++++++++++++---------------- 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 94a069f7..f1d78925 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -50,6 +50,7 @@ Other Changes: - Nav: Fixed using Alt to toggle the Menu layer when inside a Modal window. (#787) - Scrolling: Fixed SetScrollHere functions edge snapping when called during a frame where ContentSize is changing (issue introduced in 1.78). (#3452). +- InputText: Added support for Page Up/Down in InputTextMultiline(). (#3430) [@Xipiryon] - InputText: Added selection helpers in ImGuiInputTextCallbackData(). - InputText: Added ImGuiInputTextFlags_CallbackEdit to modify internally owned buffer after an edit. (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the @@ -59,8 +60,9 @@ Other Changes: - InputText: Fixed minor scrolling glitch when erasing trailing lines in InputTextMultiline(). - InputText: Fixed cursor being partially covered after using Ctrl+End key. - InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454) -- InputText: Fixed minor inconsistency when pressing Down on the last line when it doesn't have a carriage - return (it used to move to the end of the line). [@Xipiryon] +- InputText: Made pressing Down arrow on the last line when it doesn't have a carriage return not move to the end + of the line (so it is consistent with Up arrow, and behave same as Notepad and Visual Studio. Note that some + other text editors instead would move the crusor to the end of the line). [@Xipiryon] - DragFloat, DragScalar: Fixed ImGuiSliderFlags_ClampOnInput not being honored in the special case where v_min == v_max. (#3361) - SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 466bc803..c7c7d2d3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3590,6 +3590,8 @@ static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const Im #define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo #define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word #define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page +#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page #define STB_TEXTEDIT_K_SHIFT 0x400000 #define STB_TEXTEDIT_IMPLEMENTATION @@ -3856,6 +3858,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ bool clear_active_id = false; bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); + float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; + const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start); const bool init_state = (init_make_active || user_scroll_active); if (init_state && g.ActiveId != id) @@ -3916,7 +3920,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End); if (is_multiline) - g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown); // FIXME-NAV: Page up/down actually not supported yet by widget, but claim them ahead. + g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown); if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab); } @@ -4055,6 +4059,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(state != NULL); IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here. + const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); + state->Stb.row_count_per_page = row_count_per_page; + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_osx = io.ConfigMacOSXBehaviors; const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift)); @@ -4074,6 +4081,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; } + else if (IsKeyPressedMap(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; } else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } @@ -4443,12 +4452,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) { // Test if cursor is vertically visible - float scroll_y = draw_window->Scroll.y; - const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); if (cursor_offset.y - g.FontSize < scroll_y) scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); else if (cursor_offset.y - inner_size.y >= scroll_y) scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; + const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->Scroll.y = scroll_y; diff --git a/imstb_textedit.h b/imstb_textedit.h index e7205e77..76446709 100644 --- a/imstb_textedit.h +++ b/imstb_textedit.h @@ -148,6 +148,8 @@ // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right // STB_TEXTEDIT_K_UP keyboard input to move cursor up // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME @@ -170,10 +172,6 @@ // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text // -// Todo: -// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page -// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page -// // Keyboard input must be encoded as a single integer value; e.g. a character code // and some bitflags that represent shift states. to simplify the interface, SHIFT must // be a bitflag, so we can test the shifted state of cursor movements to allow selection, @@ -337,6 +335,10 @@ typedef struct // each textfield keeps its own insert mode state. to keep an app-wide // insert mode, copy this value in/out of the app state + int row_count_per_page; + // page size in number of row. + // this value MUST be set to >0 for pageup or pagedown in multilines documents. + ///////////////////// // // private data @@ -855,12 +857,16 @@ retry: break; case STB_TEXTEDIT_K_DOWN: - case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGDOWN: + case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { StbFindState find; StbTexteditRow row; - int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; + int row_count = is_page ? state->row_count_per_page : 1; - if (state->single_line) { + if (!is_page && state->single_line) { // on windows, up&down in single-line behave like left&right key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); goto retry; @@ -869,23 +875,25 @@ retry: if (sel) stb_textedit_prep_selection_at_cursor(state); else if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_last(str,state); + stb_textedit_move_to_last(str, state); // compute current position of cursor point stb_textedit_clamp(str, state); stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); - // now find character position down a row - if (find.length) { + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + int start = find.first_char + find.length; + + if (find.length == 0) + break; // [DEAR IMGUI] // going down while being on the last line shouldn't bring us to that line end if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) break; - float goal_x = state->has_preferred_x ? state->preferred_x : find.x; - float x; - int start = find.first_char + find.length; + // now find character position down a row state->cursor = start; STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); x = row.x0; @@ -907,17 +915,25 @@ retry: if (sel) state->select_end = state->cursor; + + // go to next line + find.first_char = find.first_char + find.length; + find.length = row.num_chars; } break; } case STB_TEXTEDIT_K_UP: - case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGUP: + case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { StbFindState find; StbTexteditRow row; - int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; + int row_count = is_page ? state->row_count_per_page : 1; - if (state->single_line) { + if (!is_page && state->single_line) { // on windows, up&down become left&right key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); goto retry; @@ -932,11 +948,14 @@ retry: stb_textedit_clamp(str, state); stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); - // can only go up if there's a previous row - if (find.prev_first != find.first_char) { + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + + // can only go up if there's a previous row + if (find.prev_first == find.first_char) + break; + // now find character position up a row - float goal_x = state->has_preferred_x ? state->preferred_x : find.x; - float x; state->cursor = find.prev_first; STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); x = row.x0; @@ -958,6 +977,14 @@ retry: if (sel) state->select_end = state->cursor; + + // go to previous line + // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) + prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; + while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) + --prev_scan; + find.first_char = find.prev_first; + find.prev_first = prev_scan; } break; } @@ -1081,10 +1108,6 @@ retry: state->has_preferred_x = 0; break; } - -// @TODO: -// STB_TEXTEDIT_K_PGUP - move cursor up a page -// STB_TEXTEDIT_K_PGDOWN - move cursor down a page } } @@ -1356,6 +1379,7 @@ static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_lin state->initialized = 1; state->single_line = (unsigned char) is_single_line; state->insert_mode = 0; + state->row_count_per_page = 0; } // API initialize From a58a72778179630f29dee5e292f1c5c41a79c91f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2020 12:02:19 +0200 Subject: [PATCH 15/28] Renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting 99ab5210 --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 12 +++++++----- imgui.h | 11 ++++++----- imgui_demo.cpp | 6 +++--- imgui_widgets.cpp | 10 +++++----- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f1d78925..6f2f1e3d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -42,6 +42,8 @@ Breaking Changes: It was also getting in the way of better font scaling, so let's get rid of it now! If you used DisplayOffset it was probably in association to rasterizing a font at a specific size, in which case the corresponding offset may be reported into GlyphOffset. (#1619) +- Renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), REVERTED CHANGE FROM 1.77. + For variety of reason this is more self-explanatory. Other Changes: @@ -210,6 +212,7 @@ Breaking Changes: note that this is a Beta api and will likely be reworked in order to support multi-DPI across multiple monitors. - Renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). + [NOTE: THIS WAS REVERTED IN 1.79] - Removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems. Kept inline redirection function (will obsolete). diff --git a/imgui.cpp b/imgui.cpp index a3c7e72a..679a8cde 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -372,6 +372,7 @@ CODE When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77. For varieties of reason this is more self-explanatory. - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. It was also getting in the way of better font scaling, so let's get rid of it now! - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). @@ -384,7 +385,7 @@ CODE for shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. - obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced in 1.73, was not demoed nor documented very), will be replaced by a more generic ReadOnly feature. You may use the ImGuiSliderFlags_ReadOnly internal flag in the meantime. - 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems. - - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). + - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). [NOTE: THIS WAS REVERTED IN 1.79] - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December 2017. - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular(). - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more. @@ -7920,8 +7921,9 @@ void ImGui::EndPopup() g.WithinEndChild = false; } -// Open a popup if mouse button is released over the item -bool ImGui::OpenPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags) +// Helper to open a popup if mouse button is released over the item +// - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() +bool ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiWindow* window = GImGui->CurrentWindow; int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); @@ -7938,8 +7940,8 @@ bool ImGui::OpenPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags // This is a helper to handle the simplest case of associating one named popup to one given widget. // - You can pass a NULL str_id to use the identifier of the last item. // - You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). -// - This is essentially the same as calling OpenPopupContextItem() + BeginPopup() but written to avoid -// computing the ID twice because BeginPopupContextXXX functions are called very frequently. +// - This is essentially the same as calling OpenPopupOnItemClick() + BeginPopup() but written to avoid +// computing the ID twice because BeginPopupContextXXX functions may be called very frequently. bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiWindow* window = GImGui->CurrentWindow; diff --git a/imgui.h b/imgui.h index 48889186..1ee54fb9 100644 --- a/imgui.h +++ b/imgui.h @@ -620,13 +620,13 @@ namespace ImGui // - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). // - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup(). IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). - IMGUI_API bool OpenPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. return true when just opened. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. return true when just opened. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. // Popups: open+begin combined functions helpers // - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking. // - They are convenient to easily create context menus, hence the name. // - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add ImGuiWindowFlags to the BeginPopupContextXXX functions in the future. - // - We exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter. Passing a mouse button to ImGuiPopupFlags is guaranteed to be legal. + // - IMPORTANT: we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight. IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window. IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows). @@ -1713,7 +1713,9 @@ struct ImGuiPayload #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { - // OBSOLETED in 1.78 (from August 2020) + // OBSOLETED in 1.79 (from August 2020) + static inline bool OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { return OpenPopupOnItemClick(str_id, mb); } // Renamed in 1.77, renamed back in 1.79. Sorry! + // OBSOLETED in 1.78 (from June 2020) // Old drag/sliders functions that took a 'float power = 1.0' argument instead of flags. // For shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power); @@ -1729,9 +1731,8 @@ namespace ImGui static inline bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } static inline bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); } // OBSOLETED in 1.77 (from June 2020) - static inline bool OpenPopupOnItemClick(const char* str_id = NULL, ImGuiMouseButton mb = 1) { return OpenPopupContextItem(str_id, mb); } // Passing a mouse button to ImGuiPopupFlags is legal static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } - // OBSOLETED in 1.72 (from July 2019) + // OBSOLETED in 1.72 (from April 2019) static inline void TreeAdvanceToLabelPos() { SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); } // OBSOLETED in 1.71 (from June 2019) static inline void SetNextTreeNodeOpen(bool open, ImGuiCond cond = 0) { SetNextItemOpen(open, cond); } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 9099116c..34914e0d 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2977,11 +2977,11 @@ static void ShowDemoWindowPopups() ImGui::EndPopup(); } - // We can also use OpenPopupContextItem() which is the same as BeginPopupContextItem() but without the + // We can also use OpenPopupOnItemClick() which is the same as BeginPopupContextItem() but without the // Begin() call. So here we will make it that clicking on the text field with the right mouse button (1) // will toggle the visibility of the popup above. ImGui::Text("(You can also right-click me to open the same popup as above.)"); - ImGui::OpenPopupContextItem("item context menu", 1); + ImGui::OpenPopupOnItemClick("item context menu", 1); // When used after an item that has an ID (e.g.Button), we can skip providing an ID to BeginPopupContextItem(). // BeginPopupContextItem() will use the last item ID as the popup ID. @@ -5178,7 +5178,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Context menu (under default mouse threshold) ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); if (opt_enable_context_menu && ImGui::IsMouseReleased(ImGuiMouseButton_Right) && drag_delta.x == 0.0f && drag_delta.y == 0.0f) - ImGui::OpenPopupContextItem("context"); + ImGui::OpenPopupOnItemClick("context"); if (ImGui::BeginPopup("context")) { if (adding_line) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c7c7d2d3..12394576 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4699,7 +4699,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); } if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupContextItem("context"); + OpenPopupOnItemClick("context"); } } else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) @@ -4724,7 +4724,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); } if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupContextItem("context"); + OpenPopupOnItemClick("context"); } ImGuiWindow* picker_active_window = NULL; @@ -4745,7 +4745,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag } } if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupContextItem("context"); + OpenPopupOnItemClick("context"); if (BeginPopup("picker")) { @@ -4965,7 +4965,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } } if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupContextItem("context"); + OpenPopupOnItemClick("context"); } else if (flags & ImGuiColorEditFlags_PickerHueBar) { @@ -4978,7 +4978,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl value_changed = value_changed_sv = true; } if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupContextItem("context"); + OpenPopupOnItemClick("context"); // Hue bar logic SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); From 795cf6fcb5626ddbbf995cf98b016721099ec428 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2020 15:05:04 +0200 Subject: [PATCH 16/28] Removed return value from OpenPopupOnItemClick(). Use IsWindowAppearing() after BeginPopup() for a similar result. --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 5 ++--- imgui.h | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6f2f1e3d..b2e0ccaf 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -44,6 +44,9 @@ Breaking Changes: in which case the corresponding offset may be reported into GlyphOffset. (#1619) - Renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), REVERTED CHANGE FROM 1.77. For variety of reason this is more self-explanatory. +- Removed return value from OpenPopupOnItemClick() - returned true on mouse release on item - because it + is inconsistent with other popups API and makes others misleading. It's also and unnecessary: you can + use IsWindowAppearing() after BeginPopup() for a similar result. Other Changes: diff --git a/imgui.cpp b/imgui.cpp index 679a8cde..4c867579 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -373,6 +373,7 @@ CODE You can read releases logs https://github.com/ocornut/imgui/releases for more details. - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77. For varieties of reason this is more self-explanatory. + - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on item - because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result. - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. It was also getting in the way of better font scaling, so let's get rid of it now! - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). @@ -7923,7 +7924,7 @@ void ImGui::EndPopup() // Helper to open a popup if mouse button is released over the item // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() -bool ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) +void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiWindow* window = GImGui->CurrentWindow; int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); @@ -7932,9 +7933,7 @@ bool ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) OpenPopupEx(id, popup_flags); - return true; } - return false; } // This is a helper to handle the simplest case of associating one named popup to one given widget. diff --git a/imgui.h b/imgui.h index 1ee54fb9..3b003a2b 100644 --- a/imgui.h +++ b/imgui.h @@ -620,7 +620,7 @@ namespace ImGui // - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). // - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup(). IMGUI_API void OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). - IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. return true when just opened. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) + IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. return true when just opened. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. // Popups: open+begin combined functions helpers // - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking. @@ -1714,7 +1714,7 @@ struct ImGuiPayload namespace ImGui { // OBSOLETED in 1.79 (from August 2020) - static inline bool OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { return OpenPopupOnItemClick(str_id, mb); } // Renamed in 1.77, renamed back in 1.79. Sorry! + static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! // OBSOLETED in 1.78 (from June 2020) // Old drag/sliders functions that took a 'float power = 1.0' argument instead of flags. // For shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. From afc1099fb50f1ca4fdbf7e508360d4361a896be5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2020 18:52:12 +0200 Subject: [PATCH 17/28] Tab Bar: Fixed a small bug where closing a tab that is not selected would leave a tab hole for a frame. --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index b2e0ccaf..236d278c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -81,6 +81,7 @@ Other Changes: and amends the change done in 1.76 which only affected cases were _OpenOnArrow flag was set. (This is also necessary to support full multi/range-select/drag and drop operations.) - Tab Bar: Keep tab item close button visible while dragging a tab (independent of hovering state). +- Tab Bar: Fixed a small bug where closing a tab that is not selected would leave a tab hole for a frame. - Tab Bar: Fixed a small bug where toggling a tab bar from Reorderable to not Reorderable would leave tabs reordered in the tab list popup. [@Xipiryon] - Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 12394576..a8cc2316 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7148,17 +7148,22 @@ void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) // Called on manual closure attempt void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { - if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) + if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) { // This will remove a frame of lag for selecting another tab on closure. // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure - tab->LastFrameVisible = -1; - tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; + tab->WantClose = true; + if (tab_bar->VisibleTabId == tab->ID) + { + tab->LastFrameVisible = -1; + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; + } } - else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) + else { - // Actually select before expecting closure - tab_bar->NextSelectedTabId = tab->ID; + // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup) + if (tab_bar->VisibleTabId != tab->ID) + tab_bar->NextSelectedTabId = tab->ID; } } From 27d0c3afa9327dfdb43c78f02ea3540d5e05dfa3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2020 19:46:44 +0200 Subject: [PATCH 18/28] Tab Bar: Fixed a small bug where scrolling buttons (with ImGuiTabBarFlags_FittingPolicyScroll) would generate an unnecessary extra draw call. --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 20 ++++++-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 236d278c..1c81c724 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -82,6 +82,8 @@ Other Changes: (This is also necessary to support full multi/range-select/drag and drop operations.) - Tab Bar: Keep tab item close button visible while dragging a tab (independent of hovering state). - Tab Bar: Fixed a small bug where closing a tab that is not selected would leave a tab hole for a frame. +- Tab Bar: Fixed a small bug where scrolling buttons (with ImGuiTabBarFlags_FittingPolicyScroll) would + generate an unnecessary extra draw call. - Tab Bar: Fixed a small bug where toggling a tab bar from Reorderable to not Reorderable would leave tabs reordered in the tab list popup. [@Xipiryon] - Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index a8cc2316..4a7f18c6 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7234,13 +7234,6 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) const ImVec2 backup_cursor_pos = window->DC.CursorPos; //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255)); - const ImRect avail_bar_rect = tab_bar->BarRect; - bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f))); - if (want_clip_rect) - PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true); - - ImGuiTabItem* tab_to_select = NULL; - int select_dir = 0; ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; arrow_col.w *= 0.5f; @@ -7251,30 +7244,29 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) const float backup_repeat_rate = g.IO.KeyRepeatRate; g.IO.KeyRepeatDelay = 0.250f; g.IO.KeyRepeatRate = 0.200f; - window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y); + float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); + window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) select_dir = -1; - window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y); + window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) select_dir = +1; PopStyleColor(2); g.IO.KeyRepeatRate = backup_repeat_rate; g.IO.KeyRepeatDelay = backup_repeat_delay; - if (want_clip_rect) - PopClipRect(); - + ImGuiTabItem* tab_to_scroll_to = NULL; if (select_dir != 0) if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) { int selected_order = tab_bar->GetTabOrder(tab_item); int target_order = selected_order + select_dir; - tab_to_select = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible + tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible } window->DC.CursorPos = backup_cursor_pos; tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; - return tab_to_select; + return tab_to_scroll_to; } static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) From 29836412e1f78dfda5802115b387a73460cb8563 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2020 15:49:47 +0200 Subject: [PATCH 19/28] Internals, CollapsingHeader, TabItem: Standardized using a #CLOSE id prefix for TabItem and ColllapsingHeader (same as window) --- imgui.cpp | 14 ++++++++++++++ imgui_internal.h | 1 + imgui_widgets.cpp | 5 +++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4c867579..ec107aed 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6742,6 +6742,20 @@ void ImGui::PushOverrideID(ImGuiID id) window->IDStack.push_back(id); } +// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call +// (note that when using this pattern, TestEngine's "Stack Tool" will tend to not display the intermediate stack level. +// for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little more) +ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) +{ + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); + ImGui::KeepAliveID(id); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end); +#endif + return id; +} + void ImGui::PopID() { ImGuiWindow* window = GImGui->CurrentWindow; diff --git a/imgui_internal.h b/imgui_internal.h index ec37b38b..c5b3028b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1847,6 +1847,7 @@ namespace ImGui IMGUI_API void KeepAliveID(ImGuiID id); IMGUI_API void MarkItemEdited(ImGuiID id); // Mark data associated to given item as "edited", used by IsItemDeactivatedAfterEdit() function. IMGUI_API void PushOverrideID(ImGuiID id); // Push given value as-is at the top of the ID stack (whereas PushID combines old and new hashes) + IMGUI_API ImGuiID GetIDWithSeed(const char* str_id_begin, const char* str_id_end, ImGuiID seed); // Basic Helpers for widget code IMGUI_API void ItemSize(const ImVec2& size, float text_baseline_y = -1.0f); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 4a7f18c6..abc3f16c 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5898,7 +5898,8 @@ bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags float button_size = g.FontSize; float button_x = ImMax(window->DC.LastItemRect.Min.x, window->DC.LastItemRect.Max.x - g.Style.FramePadding.x * 2.0f - button_size); float button_y = window->DC.LastItemRect.Min.y; - if (CloseButton(window->GetID((void*)((intptr_t)id + 1)), ImVec2(button_x, button_y))) + ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id); + if (CloseButton(close_button_id, ImVec2(button_x, button_y))) *p_open = false; last_item_backup.Restore(); } @@ -7539,7 +7540,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; // Render tab label, process close button - const ImGuiID close_button_id = p_open ? window->GetID((void*)((intptr_t)id + 1)) : 0; + const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible); if (just_closed && p_open != NULL) { From 4a57a982bec47910e85594dee2a0dcf061fd65a6 Mon Sep 17 00:00:00 2001 From: Louis Schnellbach Date: Mon, 3 Aug 2020 18:55:51 +0200 Subject: [PATCH 20/28] Tab Bar: Added TabItemButton(), ImGuiTabItemFlags_Leading, ImGuiTabItemFlags_Trailing + demo. (#3291) (squashed various commits by 2 authors) --- docs/CHANGELOG.txt | 5 ++ imgui.h | 8 +- imgui_demo.cpp | 85 ++++++++++++++++++ imgui_internal.h | 6 +- imgui_widgets.cpp | 210 +++++++++++++++++++++++++++++++++++---------- 5 files changed, 264 insertions(+), 50 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 1c81c724..d1f5fb2a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -86,6 +86,11 @@ Other Changes: generate an unnecessary extra draw call. - Tab Bar: Fixed a small bug where toggling a tab bar from Reorderable to not Reorderable would leave tabs reordered in the tab list popup. [@Xipiryon] +- Tab Bar: Added TabItemButton() to submit tab that behave like a button. (#3291) [@Xipiryon] +- Tab Bar: Added ImGuiTabItemFlags_Leading and ImGuiTabItemFlags_Trailing flags to position tabs or button at + either end of the tab bar. Those tabs won't be part of the scrolling region, won't shrink down, and when + reordering cannot be moving outside of their section. Most often used with TabItemButton(). (#3291) [@Xipiryon] +- Tab Bar: Added ImGuiTabItemFlags_NoReorder flag to disable reordering a given tab. - Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of a fully clipped column. (#3475) [@szreder] - Popups, Tooltips: Fix edge cases issues with positionning popups and tooltips when they are larger than diff --git a/imgui.h b/imgui.h index 3b003a2b..91a0b162 100644 --- a/imgui.h +++ b/imgui.h @@ -653,8 +653,9 @@ namespace ImGui // Tab Bars, Tabs IMGUI_API bool BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0); // create and append into a TabBar IMGUI_API void EndTabBar(); // only call EndTabBar() if BeginTabBar() returns true! - IMGUI_API bool BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0);// create a Tab. Returns true if the Tab is selected. + IMGUI_API bool BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0); // create a Tab. Returns true if the Tab is selected. IMGUI_API void EndTabItem(); // only call EndTabItem() if BeginTabItem() returns true! + IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button IMGUI_API void SetTabItemClosed(const char* tab_or_docked_window_label); // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name. // Logging/Capture @@ -954,7 +955,10 @@ enum ImGuiTabItemFlags_ ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programmatically make the tab selected when calling BeginTabItem() ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() - ImGuiTabItemFlags_NoTooltip = 1 << 4 // Disable tooltip for the given tab + ImGuiTabItemFlags_NoTooltip = 1 << 4, // Disable tooltip for the given tab + ImGuiTabItemFlags_NoReorder = 1 << 5, // Disable reordering this tab or having another tab cross over this tab + ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button) and disable resizing down + ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) and disable resizing down }; // Flags for ImGui::IsWindowFocused() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 34914e0d..42a3bf31 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2354,6 +2354,91 @@ static void ShowDemoWindowLayout() ImGui::Separator(); ImGui::TreePop(); } + + if (ImGui::TreeNode("TabItem Button")) + { + static int next_id = 0; + static ImVector tabs_list; + if (next_id == 0) // Initialize with a default tab + for (int i = 0; i < 9; i++) + tabs_list.push_back(next_id++); + + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; + ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + + static bool show_leading_button = true; + static bool show_trailing_button = true; + ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); + ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); + static bool enable_position = false; + ImGui::Checkbox("Enable Leading/Trailing TabItem()", &enable_position); + + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) + { + if (show_leading_button) + { + if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) + tabs_list.push_back(next_id++); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Add a new TabItem() in the tab bar"); + + // Popup Context also works with TabItemButton + if (ImGui::BeginPopupContextItem()) + { + ImGui::Text("I'm a popup!"); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + } + + bool close_current_tab = false; + if (show_trailing_button) + { + if (ImGui::TabItemButton("X", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) + close_current_tab = true; + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Close the currently selected TabItem()"); + } + + for (int n = 0; n < tabs_list.Size; ) + { + int mod = tabs_list[n] % 3; + ImGuiTabItemFlags flags = mod == 0 ? 0 : mod == 1 ? ImGuiTabItemFlags_Leading : ImGuiTabItemFlags_Trailing; + if (!enable_position) + flags = 0; + + char name[16]; + snprintf(name, IM_ARRAYSIZE(name), "%04d", tabs_list[n]); + bool open = true; + if (ImGui::BeginTabItem(name, &open, flags)) + { + ImGui::Text("This is the %s tab!", name); + ImGui::EndTabItem(); + + if (close_current_tab) + { + open = false; + ImGui::SetTabItemClosed(name); + } + } + + if (!open) + tabs_list.erase(tabs_list.Data + n); + else + ++n; + } + + ImGui::EndTabBar(); + } + ImGui::Separator(); + ImGui::TreePop(); + } ImGui::TreePop(); } diff --git a/imgui_internal.h b/imgui_internal.h index c5b3028b..0563e7c5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1703,7 +1703,8 @@ enum ImGuiTabBarFlagsPrivate_ // Extend ImGuiTabItemFlags_ enum ImGuiTabItemFlagsPrivate_ { - ImGuiTabItemFlags_NoCloseButton = 1 << 20 // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) + ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) + ImGuiTabItemFlags_Button = 1 << 21 // [Internal] Used by TabItemButton, change the tab item behavior to mimic a button }; // Storage for one active tab item (sizeof() 28~32 bytes) @@ -1737,7 +1738,8 @@ struct ImGuiTabBar float LastTabContentHeight; // Record the height of contents submitted below the tab bar float WidthAllTabs; // Actual width of all tabs (locked during layout) float WidthAllTabsIdeal; // Ideal width if all tabs were visible and not clipped - float OffsetNextTab; // Distance from BarRect.Min.x, incremented with each BeginTabItem() call, not used if ImGuiTabBarFlags_Reorderable if set. + float LeadingWidth; // Total width used by leading button + float TrailingWidth; // Total width used by trailing button float ScrollingAnim; float ScrollingTarget; float ScrollingTargetDistToVisibility; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index abc3f16c..79031027 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6814,7 +6814,8 @@ ImGuiTabBar::ImGuiTabBar() SelectedTabId = NextSelectedTabId = VisibleTabId = 0; CurrFrameVisible = PrevFrameVisible = -1; LastTabContentHeight = 0.0f; - WidthAllTabs = WidthAllTabsIdeal = OffsetNextTab = 0.0f; + WidthAllTabs = WidthAllTabsIdeal = 0.0f; + LeadingWidth = TrailingWidth = 0.0f; ScrollingAnim = ScrollingTarget = ScrollingTargetDistToVisibility = ScrollingSpeed = 0.0f; Flags = ImGuiTabBarFlags_None; ReorderRequestTabId = 0; @@ -6824,6 +6825,17 @@ ImGuiTabBar::ImGuiTabBar() LastTabItemIdx = -1; } +static int IMGUI_CDECL TabItemComparerByPositionAndIndex(const void* lhs, const void* rhs) +{ + const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; + const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; + const int a_position = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + const int b_position = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + if (a_position != b_position) + return a_position - b_position; + return (int)(a->IndexDuringLayout - b->IndexDuringLayout); +} + static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; @@ -6950,6 +6962,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Garbage collect by compacting list int tab_dst_n = 0; + bool need_sort_trailing_or_leading = false; for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; @@ -6963,11 +6976,21 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } if (tab_dst_n != tab_src_n) tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; + + tab_bar->Tabs[tab_dst_n].IndexDuringLayout = (ImS8)tab_dst_n; + if (tab_dst_n > 0 && (tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Leading) && !(tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Leading)) + need_sort_trailing_or_leading = true; + if (tab_dst_n > 0 && (tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Trailing) && !(tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Trailing)) + need_sort_trailing_or_leading = true; + tab_dst_n++; } if (tab_bar->Tabs.Size != tab_dst_n) tab_bar->Tabs.resize(tab_dst_n); + if (need_sort_trailing_or_leading) + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByPositionAndIndex); + // Setup next selected tab ImGuiID scroll_track_selected_tab_id = 0; if (tab_bar->NextSelectedTabId) @@ -6989,11 +7012,13 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!) const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0; if (tab_list_popup_button) - if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x! + if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; // Compute ideal widths - g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); // Reserve for maximum possible number of tabs, + int tab_n_shrinkable = 0; // ..but buttons won't shrink + tab_bar->LeadingWidth = tab_bar->TrailingWidth = 0.0f; float width_total_contents = 0.0f; ImGuiTabItem* most_recently_selected_tab = NULL; bool found_selected_tab_id = false; @@ -7003,7 +7028,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) - most_recently_selected_tab = tab; + if (!(tab->Flags & ImGuiTabItemFlags_Button)) + most_recently_selected_tab = tab; if (tab->ID == tab_bar->SelectedTabId) found_selected_tab_id = true; @@ -7014,22 +7040,29 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; - width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->ContentWidth; - - // Store data so we can build an array sorted by width if we need to shrink tabs down - g.ShrinkWidthBuffer[tab_n].Index = tab_n; - g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth; + float width = tab->ContentWidth; + if (tab->Flags & ImGuiTabItemFlags_Leading) + tab_bar->LeadingWidth += width + g.Style.ItemInnerSpacing.x; + else if (tab->Flags & ImGuiTabItemFlags_Trailing) + tab_bar->TrailingWidth += width + g.Style.ItemInnerSpacing.x; + else + { + width_total_contents += width + (tab_n_shrinkable > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); + // Store data so we can build an array sorted by width if we need to shrink tabs down + g.ShrinkWidthBuffer[tab_n_shrinkable].Index = tab_n; + g.ShrinkWidthBuffer[tab_n_shrinkable].Width = tab->ContentWidth; + tab_n_shrinkable++; + } } // Compute width - const float initial_offset_x = 0.0f; // g.Style.ItemInnerSpacing.x; - const float width_avail = ImMax(tab_bar->BarRect.GetWidth() - initial_offset_x, 0.0f); + const float width_avail = ImMax(tab_bar->BarRect.GetWidth() - tab_bar->LeadingWidth - tab_bar->TrailingWidth, 0.0f); float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f; - if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown)) + if (width_excess > 0.0f && !(tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) { // If we don't have enough room, resize down the largest tabs first - ShrinkWidths(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Size, width_excess); - for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); + for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index].Width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); } else @@ -7044,26 +7077,44 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } // Layout all active tabs - float offset_x = initial_offset_x; - float offset_x_ideal = offset_x; - tab_bar->OffsetNextTab = offset_x; // This is used by non-reorderable tab bar where the submission order is always honored. + float offset_x_button_leading = 0.0f; + float offset_x_button_trailing = tab_bar->BarRect.GetWidth() - tab_bar->TrailingWidth + g.Style.ItemInnerSpacing.x; + float offset_x_regular = tab_bar->LeadingWidth; + float offset_x_ideal = offset_x_regular; for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; - tab->Offset = offset_x; - if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) - scroll_track_selected_tab_id = tab->ID; - offset_x += tab->Width + g.Style.ItemInnerSpacing.x; - offset_x_ideal += tab->ContentWidth + g.Style.ItemInnerSpacing.x; + float width = tab->Width + g.Style.ItemInnerSpacing.x; + if (tab->Flags & ImGuiTabItemFlags_Leading) + { + tab->Offset = offset_x_button_leading; + offset_x_button_leading += width; + } + else if (tab->Flags & ImGuiTabItemFlags_Trailing) + { + tab->Offset = offset_x_button_trailing; + offset_x_button_trailing += width; + } + else + { + tab->Offset = offset_x_regular; + if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) + scroll_track_selected_tab_id = tab->ID; + offset_x_regular += width; + offset_x_ideal += tab->ContentWidth + g.Style.ItemInnerSpacing.x; + } } - tab_bar->WidthAllTabs = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f); - tab_bar->WidthAllTabsIdeal = ImMax(offset_x_ideal - g.Style.ItemInnerSpacing.x, 0.0f); + tab_bar->WidthAllTabs = ImMax(offset_x_regular - g.Style.ItemInnerSpacing.x + tab_bar->TrailingWidth, 0.0f); + tab_bar->WidthAllTabsIdeal = ImMax(offset_x_ideal - g.Style.ItemInnerSpacing.x + tab_bar->TrailingWidth, 0.0f); // Horizontal scrolling buttons const bool scrolling_buttons = (tab_bar->WidthAllTabs > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); if (scrolling_buttons) - if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x! - scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x and each trailing tab->Offset + if (scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button) + scroll_track_selected_tab_id = scroll_track_selected_tab->ID; + else + scroll_track_selected_tab_id = tab_bar->SelectedTabId = scroll_track_selected_tab->ID; // If we have lost the selected tab, select the next most recently active one if (found_selected_tab_id == false) @@ -7103,6 +7154,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; ItemSize(ImVec2(tab_bar->WidthAllTabsIdeal, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); + + window->DrawList->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Min + ImVec2(tab_bar->LeadingWidth, tab_bar->BarRect.GetHeight()), IM_COL32(255, 0, 0, 255)); + window->DrawList->AddRect(tab_bar->BarRect.Max - ImVec2(tab_bar->TrailingWidth, tab_bar->BarRect.GetHeight()), tab_bar->BarRect.Max, IM_COL32(0, 255, 0, 255)); } // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. @@ -7149,6 +7203,7 @@ void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) // Called on manual closure attempt void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { + IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button)); if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) { // This will remove a frame of lag for selecting another tab on closure. @@ -7176,9 +7231,13 @@ static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { + if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) + return; + ImGuiContext& g = *GImGui; float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) int order = tab_bar->GetTabOrder(tab); + float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f); float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f); tab_bar->ScrollingTargetDistToVisibility = 0.0f; @@ -7205,7 +7264,7 @@ void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, in bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) { ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId); - if (!tab1) + if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder)) return false; //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools @@ -7213,11 +7272,16 @@ bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) return false; + // Reordered TabItem must share the same position flags than target ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; + if (tab2->Flags & ImGuiTabItemFlags_NoReorder) + return false; + if ((tab1->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (tab2->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing))) + return false; + ImGuiTabItem item_tmp = *tab1; *tab1 = *tab2; *tab2 = item_tmp; - tab1 = tab2 = NULL; if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) MarkIniSettingsDirty(); @@ -7262,10 +7326,32 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) { int selected_order = tab_bar->GetTabOrder(tab_item); int target_order = selected_order + select_dir; - tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible + + // Skip tab item buttons until another tab item is found or end is reached + while (tab_to_scroll_to == NULL) + { + // If we are at the end of the list, still scroll to make our tab visible + tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; + + // Cross through buttons + // (even if first/last item is a button, return it so we can update the scroll) + if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button) + { + target_order += select_dir; + selected_order += select_dir; + tab_to_scroll_to = (target_order <= 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; + } + } } window->DC.CursorPos = backup_cursor_pos; + tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; + for (int i = 0; i < tab_bar->Tabs.Size; ++i) + { + ImGuiTabItem* tab = &tab_bar->Tabs[i]; + if (tab->Flags & ImGuiTabItemFlags_Trailing) + tab->Offset -= scrolling_buttons_width + 1.0f; + } return tab_to_scroll_to; } @@ -7294,6 +7380,9 @@ static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + if (tab->Flags & ImGuiTabItemFlags_Button) + continue; + const char* tab_name = tab_bar->GetTabName(tab); if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID)) tab_to_select = tab; @@ -7310,6 +7399,7 @@ static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) //------------------------------------------------------------------------- // - BeginTabItem() // - EndTabItem() +// - TabItemButton() // - TabItemEx() [Internal] // - SetTabItemClosed() // - TabItemCalcSize() [Internal] @@ -7330,6 +7420,8 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } + IM_ASSERT_USER_ERROR(!(flags & ImGuiTabItemFlags_Button), "BeginTabItem() Can't be used with button flags, use TabItemButton() instead!"); + bool ret = TabItemEx(tab_bar, label, p_open, flags); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { @@ -7358,6 +7450,22 @@ void ImGui::EndTabItem() window->IDStack.pop_back(); } +bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar, "TabItemButton() needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder); +} + bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags) { // Layout whole tab bar if not already done @@ -7383,6 +7491,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, return false; } + IM_ASSERT(!p_open || (p_open && !(flags & ImGuiTabItemFlags_Button))); // TabItemButton should not have visible content + // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented) if (flags & ImGuiTabItemFlags_NoCloseButton) p_open = NULL; @@ -7410,6 +7520,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); + const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; tab->LastFrameVisible = g.FrameCount; tab->Flags = flags; @@ -7417,20 +7528,14 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab->NameOffset = (ImS16)tab_bar->TabsNames.size(); tab_bar->TabsNames.append(label, label + strlen(label) + 1); - // If we are not reorderable, always reset offset based on submission order. - // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!) - if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) - { - tab->Offset = tab_bar->OffsetNextTab; - tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x; - } - // Update selected tab if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) - tab_bar->NextSelectedTabId = id; // New tabs gets activated + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; // New tabs gets activated if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar - tab_bar->NextSelectedTabId = id; + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; // Lock visibility // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!) @@ -7450,6 +7555,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); ItemAdd(ImRect(), id); PopItemFlag(); + if (is_tab_button) + return false; return tab_contents_visible; } @@ -7461,14 +7568,21 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Layout size.x = tab->Width; - window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + //if (is_tab_button && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown)) + if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); + else + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + size); // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) - bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x > tab_bar->BarRect.Max.x); + // If TabBar fitting policy is scroll, then just clip through the BarRect + float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->LeadingWidth; + float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->TrailingWidth; // Leading buttons will be clipped by BarRect.Max.x, trailing buttons will be clipped at BarRect.Minx + LeadingsWidth + bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x + offset_trailing > tab_bar->BarRect.Max.x); if (want_clip_rect) - PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true); + PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x + offset_leading), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x - offset_trailing, bb.Max.y), true); ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y); @@ -7483,12 +7597,12 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } // Click to Select a tab - ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap); + ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowItemOverlap); if (g.DragDropActive) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); - if (pressed) + if (pressed && !is_tab_button) tab_bar->NextSelectedTabId = id; hovered |= (g.HoveredId == id); @@ -7534,7 +7648,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1))) - tab_bar->NextSelectedTabId = id; + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; @@ -7559,6 +7674,9 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); + IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected + if (is_tab_button) + return pressed; return tab_contents_visible; } @@ -7597,7 +7715,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI const float width = bb.GetWidth(); IM_UNUSED(flags); IM_ASSERT(width > 0.0f); - const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f)); + const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); const float y1 = bb.Min.y + 1.0f; const float y2 = bb.Max.y - 1.0f; draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); From f23c39c395e4412f6f83786f591bdc4e727af203 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 27 Aug 2020 15:16:16 +0200 Subject: [PATCH 21/28] Tab Bar: Fixed handling of scrolling policy with leading/trailing tabs. + warning fixes + bunch of renaming. (#3291) Demo tweaks. --- imgui.cpp | 11 ++++- imgui_demo.cpp | 72 ++++++++++------------------ imgui_internal.h | 7 +-- imgui_widgets.cpp | 120 +++++++++++++++++++++++++++------------------- 4 files changed, 109 insertions(+), 101 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index ec107aed..427fffc9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10559,6 +10559,15 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } bool open = ImGui::TreeNode(tab_bar, "%s", buf); if (!is_active) { PopStyleColor(); } + if (is_active && ImGui::IsItemHovered()) + { + ImDrawList* draw_list = ImGui::GetForegroundDrawList(); + draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); + if (tab_bar->WidthLeading > 0.0f) + draw_list->AddLine(ImVec2(tab_bar->BarRect.Min.x + tab_bar->WidthLeading, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Min.x + tab_bar->WidthLeading, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + if (tab_bar->WidthTrailing > 0.0f) + draw_list->AddLine(ImVec2(tab_bar->BarRect.Max.x - tab_bar->WidthTrailing, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x - tab_bar->WidthTrailing, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + } if (open) { for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) @@ -10567,7 +10576,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGui::PushID(tab); if (ImGui::SmallButton("<")) { TabBarQueueReorder(tab_bar, tab, -1); } ImGui::SameLine(0, 2); if (ImGui::SmallButton(">")) { TabBarQueueReorder(tab_bar, tab, +1); } ImGui::SameLine(); - ImGui::Text("%02d%c Tab 0x%08X '%s'", tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, (tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : ""); + ImGui::Text("%02d%c Tab 0x%08X '%s' Offset: %.1f, Width: %.1f/%.1f", tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, (tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "", tab->Offset, tab->Width, tab->ContentWidth); ImGui::PopID(); } ImGui::TreePop(); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 42a3bf31..3cc153cb 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2355,16 +2355,15 @@ static void ShowDemoWindowLayout() ImGui::TreePop(); } - if (ImGui::TreeNode("TabItem Button")) + if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags")) { - static int next_id = 0; - static ImVector tabs_list; - if (next_id == 0) // Initialize with a default tab - for (int i = 0; i < 9; i++) - tabs_list.push_back(next_id++); + static ImVector active_tabs; + static int next_tab_id = 0; + if (next_tab_id == 0) // Initialize with some default tabs + for (int i = 0; i < 3; i++) + active_tabs.push_back(next_tab_id++); static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; - ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_Reorderable); ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); @@ -2375,63 +2374,40 @@ static void ShowDemoWindowLayout() static bool show_trailing_button = true; ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); - static bool enable_position = false; - ImGui::Checkbox("Enable Leading/Trailing TabItem()", &enable_position); if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { - if (show_leading_button) + // Demo Leading Tabs: click the "?" button to open a menu + // Note that it is possible to submit regular non-button tabs with Leading/Trailing flags, + // or Button without Leading/Trailing flags... but they tend to make more sense together. + if (show_leading_button && ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) + ImGui::OpenPopup("MyHelpMenu"); + if (ImGui::BeginPopup("MyHelpMenu")) { - if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) - tabs_list.push_back(next_id++); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Add a new TabItem() in the tab bar"); - - // Popup Context also works with TabItemButton - if (ImGui::BeginPopupContextItem()) - { - ImGui::Text("I'm a popup!"); - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } + ImGui::Selectable("Hello!"); + ImGui::EndPopup(); } - bool close_current_tab = false; - if (show_trailing_button) - { - if (ImGui::TabItemButton("X", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) - close_current_tab = true; - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Close the currently selected TabItem()"); - } + // Demo Trailing Tabs: click the "+" button to add a new tab (in your app you may want to use a font icon instead of the "+") + if (show_trailing_button && ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) + active_tabs.push_back(next_tab_id++); - for (int n = 0; n < tabs_list.Size; ) + // Submit our regular tabs + for (int n = 0; n < active_tabs.Size; ) { - int mod = tabs_list[n] % 3; - ImGuiTabItemFlags flags = mod == 0 ? 0 : mod == 1 ? ImGuiTabItemFlags_Leading : ImGuiTabItemFlags_Trailing; - if (!enable_position) - flags = 0; - - char name[16]; - snprintf(name, IM_ARRAYSIZE(name), "%04d", tabs_list[n]); bool open = true; - if (ImGui::BeginTabItem(name, &open, flags)) + char name[16]; + snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]); + if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None)) { ImGui::Text("This is the %s tab!", name); ImGui::EndTabItem(); - - if (close_current_tab) - { - open = false; - ImGui::SetTabItemClosed(name); - } } if (!open) - tabs_list.erase(tabs_list.Data + n); + active_tabs.erase(active_tabs.Data + n); else - ++n; + n++; } ImGui::EndTabBar(); diff --git a/imgui_internal.h b/imgui_internal.h index 0563e7c5..5a3d468b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1719,9 +1719,10 @@ struct ImGuiTabItem float ContentWidth; // Width of actual contents, stored during BeginTabItem() call ImS16 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS8 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable + ImS8 IndexDuringLayout; // Index only used during TabBarLayout() bool WantClose; // Marked as closed by SetTabItemClosed() - ImGuiTabItem() { ID = 0; Flags = ImGuiTabItemFlags_None; LastFrameVisible = LastFrameSelected = -1; NameOffset = -1; Offset = Width = ContentWidth = 0.0f; BeginOrder = -1; WantClose = false; } + ImGuiTabItem() { ID = 0; Flags = ImGuiTabItemFlags_None; LastFrameVisible = LastFrameSelected = -1; NameOffset = -1; Offset = Width = ContentWidth = 0.0f; BeginOrder = -1; IndexDuringLayout = -1; WantClose = false; } }; // Storage for a tab bar (sizeof() 92~96 bytes) @@ -1738,8 +1739,8 @@ struct ImGuiTabBar float LastTabContentHeight; // Record the height of contents submitted below the tab bar float WidthAllTabs; // Actual width of all tabs (locked during layout) float WidthAllTabsIdeal; // Ideal width if all tabs were visible and not clipped - float LeadingWidth; // Total width used by leading button - float TrailingWidth; // Total width used by trailing button + float WidthLeading; // Total width used by leading button + float WidthTrailing; // Total width used by trailing button float ScrollingAnim; float ScrollingTarget; float ScrollingTargetDistToVisibility; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 79031027..35c47793 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6815,7 +6815,7 @@ ImGuiTabBar::ImGuiTabBar() CurrFrameVisible = PrevFrameVisible = -1; LastTabContentHeight = 0.0f; WidthAllTabs = WidthAllTabsIdeal = 0.0f; - LeadingWidth = TrailingWidth = 0.0f; + WidthLeading = WidthTrailing = 0.0f; ScrollingAnim = ScrollingTarget = ScrollingTargetDistToVisibility = ScrollingSpeed = 0.0f; Flags = ImGuiTabBarFlags_None; ReorderRequestTabId = 0; @@ -7015,11 +7015,15 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + // Reserve shrink buffer for maximum possible number of tabs (but tabs with Trailing/Leading won't shrink) + int tab_n_shrinkable = 0; + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + // Compute ideal widths - g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); // Reserve for maximum possible number of tabs, - int tab_n_shrinkable = 0; // ..but buttons won't shrink - tab_bar->LeadingWidth = tab_bar->TrailingWidth = 0.0f; - float width_total_contents = 0.0f; + float width_central = 0.0f; + tab_bar->WidthLeading = tab_bar->WidthTrailing = 0.0f; + const float tab_max_width = TabBarCalcMaxTabWidth(); + ImGuiTabItem* most_recently_selected_tab = NULL; bool found_selected_tab_id = false; for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) @@ -7027,9 +7031,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); - if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) - if (!(tab->Flags & ImGuiTabItemFlags_Button)) - most_recently_selected_tab = tab; + if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button)) + most_recently_selected_tab = tab; if (tab->ID == tab_bar->SelectedTabId) found_selected_tab_id = true; @@ -7042,79 +7045,95 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) float width = tab->ContentWidth; if (tab->Flags & ImGuiTabItemFlags_Leading) - tab_bar->LeadingWidth += width + g.Style.ItemInnerSpacing.x; + { + tab_bar->WidthLeading += width + g.Style.ItemInnerSpacing.x; + } else if (tab->Flags & ImGuiTabItemFlags_Trailing) - tab_bar->TrailingWidth += width + g.Style.ItemInnerSpacing.x; + { + tab_bar->WidthTrailing += width + g.Style.ItemInnerSpacing.x; + } else { - width_total_contents += width + (tab_n_shrinkable > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); - // Store data so we can build an array sorted by width if we need to shrink tabs down - g.ShrinkWidthBuffer[tab_n_shrinkable].Index = tab_n; + width = ImMin(tab->ContentWidth, tab_max_width); + width_central += width + (tab_n_shrinkable > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); + g.ShrinkWidthBuffer[tab_n_shrinkable].Index = tab_n; // Store data so we can build an array sorted by width if we need to shrink tabs down g.ShrinkWidthBuffer[tab_n_shrinkable].Width = tab->ContentWidth; tab_n_shrinkable++; } + + IM_ASSERT(width > 0.0f); + tab->Width = width; } // Compute width - const float width_avail = ImMax(tab_bar->BarRect.GetWidth() - tab_bar->LeadingWidth - tab_bar->TrailingWidth, 0.0f); - float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f; + const float width_avail = ImMax(tab_bar->BarRect.GetWidth() - tab_bar->WidthLeading - tab_bar->WidthTrailing, 0.0f); + float width_excess = (width_avail < width_central) ? (width_central - width_avail) : 0.0f; if (width_excess > 0.0f && !(tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) { // If we don't have enough room, resize down the largest tabs first ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index].Width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); - } - else - { - const float tab_max_width = TabBarCalcMaxTabWidth(); + width_central -= width_excess; +#if 0 + width_central = (tab_bar->WidthLeading > 0.0f) ? -g.Style.ItemInnerSpacing.x : 0.0f; for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; - tab->Width = ImMin(tab->ContentWidth, tab_max_width); - IM_ASSERT(tab->Width > 0.0f); + if ((tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0) + width_central += tab->Width + g.Style.ItemInnerSpacing.x; } +#endif } // Layout all active tabs - float offset_x_button_leading = 0.0f; - float offset_x_button_trailing = tab_bar->BarRect.GetWidth() - tab_bar->TrailingWidth + g.Style.ItemInnerSpacing.x; - float offset_x_regular = tab_bar->LeadingWidth; - float offset_x_ideal = offset_x_regular; + float offset_x_pos_leading = 0.0f; + float offset_x_pos_central = tab_bar->WidthLeading; +#if 0 + float offset_x_pos_trailing = tab_bar->BarRect.GetWidth() - tab_bar->WidthTrailing + g.Style.ItemInnerSpacing.x; // Right-most layout +#else + float offset_x_pos_trailing = tab_bar->WidthLeading + width_central; // Trailing layout + if (offset_x_pos_trailing > 0.0f) + offset_x_pos_trailing += g.Style.ItemInnerSpacing.x; +#endif + + float width_ideal = tab_bar->WidthLeading; for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; float width = tab->Width + g.Style.ItemInnerSpacing.x; if (tab->Flags & ImGuiTabItemFlags_Leading) { - tab->Offset = offset_x_button_leading; - offset_x_button_leading += width; + tab->Offset = offset_x_pos_leading; + offset_x_pos_leading += width; } else if (tab->Flags & ImGuiTabItemFlags_Trailing) { - tab->Offset = offset_x_button_trailing; - offset_x_button_trailing += width; + tab->Offset = offset_x_pos_trailing; + offset_x_pos_trailing += width; } else { - tab->Offset = offset_x_regular; + tab->Offset = offset_x_pos_central; if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) scroll_track_selected_tab_id = tab->ID; - offset_x_regular += width; - offset_x_ideal += tab->ContentWidth + g.Style.ItemInnerSpacing.x; + offset_x_pos_central += width; + width_ideal += tab->ContentWidth + g.Style.ItemInnerSpacing.x; } } - tab_bar->WidthAllTabs = ImMax(offset_x_regular - g.Style.ItemInnerSpacing.x + tab_bar->TrailingWidth, 0.0f); - tab_bar->WidthAllTabsIdeal = ImMax(offset_x_ideal - g.Style.ItemInnerSpacing.x + tab_bar->TrailingWidth, 0.0f); + tab_bar->WidthAllTabs = ImMax(offset_x_pos_central - g.Style.ItemInnerSpacing.x + tab_bar->WidthTrailing, 0.0f); + tab_bar->WidthAllTabsIdeal = ImMax(width_ideal - g.Style.ItemInnerSpacing.x + tab_bar->WidthTrailing, 0.0f); // Horizontal scrolling buttons const bool scrolling_buttons = (tab_bar->WidthAllTabs > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); if (scrolling_buttons) if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x and each trailing tab->Offset + { if (scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button) scroll_track_selected_tab_id = scroll_track_selected_tab->ID; else scroll_track_selected_tab_id = tab_bar->SelectedTabId = scroll_track_selected_tab->ID; + } // If we have lost the selected tab, select the next most recently active one if (found_selected_tab_id == false) @@ -7154,9 +7173,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; ItemSize(ImVec2(tab_bar->WidthAllTabsIdeal, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); - - window->DrawList->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Min + ImVec2(tab_bar->LeadingWidth, tab_bar->BarRect.GetHeight()), IM_COL32(255, 0, 0, 255)); - window->DrawList->AddRect(tab_bar->BarRect.Max - ImVec2(tab_bar->TrailingWidth, tab_bar->BarRect.GetHeight()), tab_bar->BarRect.Max, IM_COL32(0, 255, 0, 255)); } // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. @@ -7238,18 +7254,24 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) int order = tab_bar->GetTabOrder(tab); - float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f); - float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f); + // Scrolling happens only in the central section (leading/trailing sections are not scrolling) + float scrollable_width = tab_bar->BarRect.GetWidth() - tab_bar->WidthLeading - tab_bar->WidthTrailing; + + // We make all tabs positions all relative WidthLeading to make code simpler + float tab_x1 = tab->Offset - tab_bar->WidthLeading + (order > 0 ? -margin : 0.0f); + float tab_x2 = tab->Offset - tab_bar->WidthLeading + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f); tab_bar->ScrollingTargetDistToVisibility = 0.0f; - if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= tab_bar->BarRect.GetWidth())) + if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) { + // Scroll to the left tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f); tab_bar->ScrollingTarget = tab_x1; } - else if (tab_bar->ScrollingTarget < tab_x2 - tab_bar->BarRect.GetWidth()) + else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width) { - tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - tab_bar->BarRect.GetWidth()) - tab_bar->ScrollingAnim, 0.0f); - tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth(); + // Scroll to the right + tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f); + tab_bar->ScrollingTarget = tab_x2 - scrollable_width; } } @@ -7420,7 +7442,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } - IM_ASSERT_USER_ERROR(!(flags & ImGuiTabItemFlags_Button), "BeginTabItem() Can't be used with button flags, use TabItemButton() instead!"); + IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! bool ret = TabItemEx(tab_bar, label, p_open, flags); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) @@ -7460,7 +7482,7 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { - IM_ASSERT_USER_ERROR(tab_bar, "TabItemButton() needs to be called between BeginTabBar() and EndTabBar()!"); + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder); @@ -7491,7 +7513,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, return false; } - IM_ASSERT(!p_open || (p_open && !(flags & ImGuiTabItemFlags_Button))); // TabItemButton should not have visible content + IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button)); + IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented) if (flags & ImGuiTabItemFlags_NoCloseButton) @@ -7568,7 +7591,6 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Layout size.x = tab->Width; - //if (is_tab_button && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown)) if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); else @@ -7578,8 +7600,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) // If TabBar fitting policy is scroll, then just clip through the BarRect - float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->LeadingWidth; - float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->TrailingWidth; // Leading buttons will be clipped by BarRect.Max.x, trailing buttons will be clipped at BarRect.Minx + LeadingsWidth + float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->WidthLeading; + float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->WidthTrailing; // Leading buttons will be clipped by BarRect.Max.x, trailing buttons will be clipped at BarRect.Minx + LeadingsWidth bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x + offset_trailing > tab_bar->BarRect.Max.x); if (want_clip_rect) PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x + offset_leading), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x - offset_trailing, bb.Max.y), true); From 7ac16c02cc1e1c6bcbdecdd5ac0746b157395084 Mon Sep 17 00:00:00 2001 From: Louis Schnellbach Date: Thu, 27 Aug 2020 16:18:25 +0200 Subject: [PATCH 22/28] Tab Bar: Fix multiple width and position computation issue. (#3291) --- imgui.cpp | 8 +- imgui_internal.h | 15 ++- imgui_widgets.cpp | 234 ++++++++++++++++++++++++++++------------------ 3 files changed, 159 insertions(+), 98 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 427fffc9..4518cd80 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10563,10 +10563,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImDrawList* draw_list = ImGui::GetForegroundDrawList(); draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); - if (tab_bar->WidthLeading > 0.0f) - draw_list->AddLine(ImVec2(tab_bar->BarRect.Min.x + tab_bar->WidthLeading, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Min.x + tab_bar->WidthLeading, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); - if (tab_bar->WidthTrailing > 0.0f) - draw_list->AddLine(ImVec2(tab_bar->BarRect.Max.x - tab_bar->WidthTrailing, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x - tab_bar->WidthTrailing, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + if (tab_bar->LeadingSection.Width > 0.0f) + draw_list->AddLine(ImVec2(tab_bar->BarRect.Min.x + tab_bar->LeadingSection.Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Min.x + tab_bar->LeadingSection.Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + if (tab_bar->TrailingSection.Width > 0.0f) + draw_list->AddLine(ImVec2(tab_bar->BarRect.Max.x - tab_bar->TrailingSection.Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x - tab_bar->TrailingSection.Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); } if (open) { diff --git a/imgui_internal.h b/imgui_internal.h index 5a3d468b..bb633253 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1725,6 +1725,16 @@ struct ImGuiTabItem ImGuiTabItem() { ID = 0; Flags = ImGuiTabItemFlags_None; LastFrameVisible = LastFrameSelected = -1; NameOffset = -1; Offset = Width = ContentWidth = 0.0f; BeginOrder = -1; IndexDuringLayout = -1; WantClose = false; } }; +struct TabBarLayoutSection +{ + ImGuiTabItem* Tabs; // Pointer to the first tab_bar->Tabs matching the section + int TabCount; + float Width; + float WidthIdeal; + float InnerSpacing; // Horizontal ItemInnerSpacing, used by Leading/Trailing section, to correctly offset from Central section + TabBarLayoutSection() { Tabs = NULL; TabCount = 0; Width = 0.0f; WidthIdeal = 0.0f; InnerSpacing = 0.0f; } +}; + // Storage for a tab bar (sizeof() 92~96 bytes) struct ImGuiTabBar { @@ -1739,8 +1749,6 @@ struct ImGuiTabBar float LastTabContentHeight; // Record the height of contents submitted below the tab bar float WidthAllTabs; // Actual width of all tabs (locked during layout) float WidthAllTabsIdeal; // Ideal width if all tabs were visible and not clipped - float WidthLeading; // Total width used by leading button - float WidthTrailing; // Total width used by trailing button float ScrollingAnim; float ScrollingTarget; float ScrollingTargetDistToVisibility; @@ -1749,6 +1757,9 @@ struct ImGuiTabBar ImGuiID ReorderRequestTabId; ImS8 ReorderRequestDir; ImS8 TabsActiveCount; // Number of tabs submitted this frame. + TabBarLayoutSection LeadingSection; + TabBarLayoutSection CentralSection; + TabBarLayoutSection TrailingSection; bool WantLayout; bool VisibleTabWasSubmitted; short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 35c47793..0c81dd0b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6785,6 +6785,8 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - BeginTabBarEx() [Internal] // - EndTabBar() // - TabBarLayout() [Internal] +// - TabBarLayoutComputeTabsWidth() [Internal] +// - TabBarLayoutActiveTabsForSection() [Internal] // - TabBarCalcTabID() [Internal] // - TabBarCalcMaxTabWidth() [Internal] // - TabBarFindTabById() [Internal] @@ -6800,6 +6802,8 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); + static void TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width); + static float TabBarLayoutActiveTabsForSection(ImGuiTabBar* tab_bar, TabBarLayoutSection* section, float next_tab_offset); static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); @@ -6815,7 +6819,6 @@ ImGuiTabBar::ImGuiTabBar() CurrFrameVisible = PrevFrameVisible = -1; LastTabContentHeight = 0.0f; WidthAllTabs = WidthAllTabsIdeal = 0.0f; - WidthLeading = WidthTrailing = 0.0f; ScrollingAnim = ScrollingTarget = ScrollingTargetDistToVisibility = ScrollingSpeed = 0.0f; Flags = ImGuiTabBarFlags_None; ReorderRequestTabId = 0; @@ -6857,6 +6860,12 @@ static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) return ImGuiPtrOrIndex(tab_bar); } +static ImVec2 GetTabBarScrollingButtonSize() +{ + ImGuiContext& g = *GImGui; + return ImVec2(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); +} + bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; @@ -6963,6 +6972,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Garbage collect by compacting list int tab_dst_n = 0; bool need_sort_trailing_or_leading = false; + tab_bar->LeadingSection.TabCount = tab_bar->CentralSection.TabCount = tab_bar->TrailingSection.TabCount = 0; for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; @@ -6978,11 +6988,20 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; tab_bar->Tabs[tab_dst_n].IndexDuringLayout = (ImS8)tab_dst_n; - if (tab_dst_n > 0 && (tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Leading) && !(tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Leading)) + bool is_leading = (tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Leading) != 0; + bool is_trailing = (tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Trailing) != 0; + if (tab_dst_n > 0 && is_leading && !(tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Leading)) need_sort_trailing_or_leading = true; - if (tab_dst_n > 0 && (tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Trailing) && !(tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Trailing)) + if (tab_dst_n > 0 && (tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Trailing) && !is_trailing) need_sort_trailing_or_leading = true; + if (is_leading) + tab_bar->LeadingSection.TabCount++; + else if (is_trailing) + tab_bar->TrailingSection.TabCount++; + else + tab_bar->CentralSection.TabCount++; + tab_dst_n++; } if (tab_bar->Tabs.Size != tab_dst_n) @@ -6991,6 +7010,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (need_sort_trailing_or_leading) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByPositionAndIndex); + tab_bar->LeadingSection.Tabs = tab_bar->Tabs.Data; + tab_bar->CentralSection.Tabs = tab_bar->Tabs.Data + tab_bar->LeadingSection.TabCount; + tab_bar->TrailingSection.Tabs = tab_bar->Tabs.Data + tab_bar->LeadingSection.TabCount + tab_bar->CentralSection.TabCount; + // Setup next selected tab ImGuiID scroll_track_selected_tab_id = 0; if (tab_bar->NextSelectedTabId) @@ -7015,13 +7038,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; - // Reserve shrink buffer for maximum possible number of tabs (but tabs with Trailing/Leading won't shrink) - int tab_n_shrinkable = 0; + // Reserve shrink buffer for maximum possible number of tabs g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); // Compute ideal widths - float width_central = 0.0f; - tab_bar->WidthLeading = tab_bar->WidthTrailing = 0.0f; + tab_bar->LeadingSection.Width = tab_bar->CentralSection.Width = tab_bar->TrailingSection.Width = 0.0f; const float tab_max_width = TabBarCalcMaxTabWidth(); ImGuiTabItem* most_recently_selected_tab = NULL; @@ -7035,6 +7056,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) most_recently_selected_tab = tab; if (tab->ID == tab_bar->SelectedTabId) found_selected_tab_id = true; + if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) + scroll_track_selected_tab_id = tab->ID; // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar. // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, @@ -7043,91 +7066,44 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; - float width = tab->ContentWidth; + float width = ImMin(tab->ContentWidth, tab_max_width); if (tab->Flags & ImGuiTabItemFlags_Leading) - { - tab_bar->WidthLeading += width + g.Style.ItemInnerSpacing.x; - } + tab_bar->LeadingSection.Width += width + (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); else if (tab->Flags & ImGuiTabItemFlags_Trailing) - { - tab_bar->WidthTrailing += width + g.Style.ItemInnerSpacing.x; - } + tab_bar->TrailingSection.Width += width + (tab_n > tab_bar->LeadingSection.TabCount + tab_bar->CentralSection.TabCount ? g.Style.ItemInnerSpacing.x : 0.0f); else - { - width = ImMin(tab->ContentWidth, tab_max_width); - width_central += width + (tab_n_shrinkable > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); - g.ShrinkWidthBuffer[tab_n_shrinkable].Index = tab_n; // Store data so we can build an array sorted by width if we need to shrink tabs down - g.ShrinkWidthBuffer[tab_n_shrinkable].Width = tab->ContentWidth; - tab_n_shrinkable++; - } + tab_bar->CentralSection.Width += width + (tab_n > tab_bar->LeadingSection.TabCount ? g.Style.ItemInnerSpacing.x : 0.0f); + + g.ShrinkWidthBuffer[tab_n].Index = tab_n; // Store data so we can build an array sorted by width if we need to shrink tabs down + g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth; IM_ASSERT(width > 0.0f); tab->Width = width; } + tab_bar->LeadingSection.WidthIdeal = tab_bar->LeadingSection.Width; + tab_bar->CentralSection.WidthIdeal = tab_bar->CentralSection.Width; + tab_bar->TrailingSection.WidthIdeal = tab_bar->TrailingSection.Width; - // Compute width - const float width_avail = ImMax(tab_bar->BarRect.GetWidth() - tab_bar->WidthLeading - tab_bar->WidthTrailing, 0.0f); - float width_excess = (width_avail < width_central) ? (width_central - width_avail) : 0.0f; - if (width_excess > 0.0f && !(tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) - { - // If we don't have enough room, resize down the largest tabs first - ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); - for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) - tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index].Width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); - width_central -= width_excess; -#if 0 - width_central = (tab_bar->WidthLeading > 0.0f) ? -g.Style.ItemInnerSpacing.x : 0.0f; - for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) - { - ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; - if ((tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0) - width_central += tab->Width + g.Style.ItemInnerSpacing.x; - } -#endif - } + // We want to know here if we'll need the scrolling buttons, to adjust available width with resizable leading/trailing + bool scrolling_buttons = (tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); + float scrolling_buttons_width = GetTabBarScrollingButtonSize().x * 2.0f; + TabBarLayoutComputeTabsWidth(tab_bar, scrolling_buttons, scrolling_buttons_width); // Layout all active tabs - float offset_x_pos_leading = 0.0f; - float offset_x_pos_central = tab_bar->WidthLeading; -#if 0 - float offset_x_pos_trailing = tab_bar->BarRect.GetWidth() - tab_bar->WidthTrailing + g.Style.ItemInnerSpacing.x; // Right-most layout -#else - float offset_x_pos_trailing = tab_bar->WidthLeading + width_central; // Trailing layout - if (offset_x_pos_trailing > 0.0f) - offset_x_pos_trailing += g.Style.ItemInnerSpacing.x; -#endif + tab_bar->WidthAllTabs = tab_bar->WidthAllTabsIdeal = 0.0f; + float next_tab_offset = 0.0f; - float width_ideal = tab_bar->WidthLeading; - for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) - { - ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; - float width = tab->Width + g.Style.ItemInnerSpacing.x; - if (tab->Flags & ImGuiTabItemFlags_Leading) - { - tab->Offset = offset_x_pos_leading; - offset_x_pos_leading += width; - } - else if (tab->Flags & ImGuiTabItemFlags_Trailing) - { - tab->Offset = offset_x_pos_trailing; - offset_x_pos_trailing += width; - } - else - { - tab->Offset = offset_x_pos_central; - if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) - scroll_track_selected_tab_id = tab->ID; - offset_x_pos_central += width; - width_ideal += tab->ContentWidth + g.Style.ItemInnerSpacing.x; - } - } - tab_bar->WidthAllTabs = ImMax(offset_x_pos_central - g.Style.ItemInnerSpacing.x + tab_bar->WidthTrailing, 0.0f); - tab_bar->WidthAllTabsIdeal = ImMax(width_ideal - g.Style.ItemInnerSpacing.x + tab_bar->WidthTrailing, 0.0f); + // TabBarLayoutActiveTabsForSection will also alter tab_bar->WidthAllTabs and WidthAllTabsIdeal + next_tab_offset = TabBarLayoutActiveTabsForSection(tab_bar, &tab_bar->LeadingSection, next_tab_offset); // Leading Section + next_tab_offset = TabBarLayoutActiveTabsForSection(tab_bar, &tab_bar->CentralSection, next_tab_offset); // Central Section + + // TabBarScrollingButtons will alter BarRect.Max.x, so we need to anticipate BarRect width reduction + next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - tab_bar->TrailingSection.Width - (scrolling_buttons ? scrolling_buttons_width + 1.0f : 0.0f), next_tab_offset); + TabBarLayoutActiveTabsForSection(tab_bar, &tab_bar->TrailingSection, next_tab_offset); // Trailing Section // Horizontal scrolling buttons - const bool scrolling_buttons = (tab_bar->WidthAllTabs > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); if (scrolling_buttons) - if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x and each trailing tab->Offset + if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x { if (scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button) scroll_track_selected_tab_id = scroll_track_selected_tab->ID; @@ -7173,6 +7149,86 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; ItemSize(ImVec2(tab_bar->WidthAllTabsIdeal, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); + +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (g.IO.KeyAlt) + { + window->DrawList->AddRect(tab_bar->BarRect.Min, ImVec2(tab_bar->BarRect.Min.x + tab_bar->LeadingSection.Width, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); + window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - tab_bar->TrailingSection.Width, tab_bar->BarRect.Min.y), tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); + } +#endif +} + +static void ImGui::TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width) +{ + ImGuiContext& g = *GImGui; + + // Compute Leading/Trailing relative additional horizontal inner spacing + float leading_trailing_common_inner_space = (tab_bar->LeadingSection.TabCount > 0 && tab_bar->TrailingSection.TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); + bool resizing_leading_trailing_only = (tab_bar->LeadingSection.Width + tab_bar->TrailingSection.Width + leading_trailing_common_inner_space) > (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)); + + tab_bar->LeadingSection.InnerSpacing = tab_bar->LeadingSection.TabCount > 0 && (tab_bar->CentralSection.TabCount + tab_bar->TrailingSection.TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + tab_bar->CentralSection.InnerSpacing = tab_bar->CentralSection.TabCount > 0 && tab_bar->TrailingSection.TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + tab_bar->TrailingSection.InnerSpacing = 0.0f; + + // Compute width + float width_excess = resizing_leading_trailing_only + ? (tab_bar->LeadingSection.Width + tab_bar->TrailingSection.Width + leading_trailing_common_inner_space) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)) + : ImMax(tab_bar->CentralSection.Width + tab_bar->CentralSection.InnerSpacing - (tab_bar->BarRect.GetWidth() - tab_bar->LeadingSection.Width - tab_bar->LeadingSection.InnerSpacing - tab_bar->TrailingSection.Width - tab_bar->TrailingSection.InnerSpacing), 0.0f); + + if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || resizing_leading_trailing_only) + { + // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data + if (resizing_leading_trailing_only) + memmove(g.ShrinkWidthBuffer.Data + tab_bar->LeadingSection.TabCount, g.ShrinkWidthBuffer.Data + tab_bar->LeadingSection.TabCount + tab_bar->CentralSection.TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->TrailingSection.TabCount); + else + memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->LeadingSection.TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->CentralSection.TabCount); + int tab_n_shrinkable = (resizing_leading_trailing_only ? tab_bar->LeadingSection.TabCount + tab_bar->TrailingSection.TabCount : tab_bar->CentralSection.TabCount); + + ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); + + // Total Leading and Trailing shrink values can be different, we need to keep track of how much each section was shrinked + float leading_excess = 0.0f; + float trailing_excess = 0.0f; + for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) + { + float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); + ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; + + if (tab->Flags & ImGuiTabItemFlags_Leading) + leading_excess += (tab->Width - shrinked_width); + else if (tab->Flags & ImGuiTabItemFlags_Trailing) + trailing_excess += (tab->Width - shrinked_width); + + tab->Width = shrinked_width; + } + + if (resizing_leading_trailing_only) + { + tab_bar->LeadingSection.Width -= leading_excess; + tab_bar->TrailingSection.Width -= trailing_excess; + } + else + { + tab_bar->CentralSection.Width -= width_excess; + } + } +} + +static float ImGui::TabBarLayoutActiveTabsForSection(ImGuiTabBar* tab_bar, TabBarLayoutSection* section, float next_tab_offset) +{ + ImGuiContext& g = *GImGui; + + for(int i = 0; i < section->TabCount; ++i) + { + ImGuiTabItem* tab = section->Tabs + i; + tab->Offset = next_tab_offset; + next_tab_offset += tab->Width + (i < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + } + tab_bar->WidthAllTabs += ImMax(section->Width + section->InnerSpacing, 0.0f); + tab_bar->WidthAllTabsIdeal += ImMax(section->WidthIdeal + section->InnerSpacing, 0.0f); + + return next_tab_offset + section->InnerSpacing; } // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. @@ -7255,11 +7311,11 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) int order = tab_bar->GetTabOrder(tab); // Scrolling happens only in the central section (leading/trailing sections are not scrolling) - float scrollable_width = tab_bar->BarRect.GetWidth() - tab_bar->WidthLeading - tab_bar->WidthTrailing; + float scrollable_width = tab_bar->BarRect.GetWidth() - tab_bar->LeadingSection.Width - tab_bar->TrailingSection.Width - tab_bar->CentralSection.InnerSpacing; - // We make all tabs positions all relative WidthLeading to make code simpler - float tab_x1 = tab->Offset - tab_bar->WidthLeading + (order > 0 ? -margin : 0.0f); - float tab_x2 = tab->Offset - tab_bar->WidthLeading + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f); + // We make all tabs positions all relative LeadingSection.Width to make code simpler + float tab_x1 = tab->Offset - tab_bar->LeadingSection.Width + (order > tab_bar->LeadingSection.TabCount - 1 ? -margin : 0.0f); + float tab_x2 = tab->Offset - tab_bar->LeadingSection.Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - tab_bar->TrailingSection.TabCount ? margin : 1.0f); tab_bar->ScrollingTargetDistToVisibility = 0.0f; if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) { @@ -7315,7 +7371,7 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); + const ImVec2 arrow_button_size = GetTabBarScrollingButtonSize(); const float scrolling_buttons_width = arrow_button_size.x * 2.0f; const ImVec2 backup_cursor_pos = window->DC.CursorPos; @@ -7368,12 +7424,6 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) window->DC.CursorPos = backup_cursor_pos; tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; - for (int i = 0; i < tab_bar->Tabs.Size; ++i) - { - ImGuiTabItem* tab = &tab_bar->Tabs[i]; - if (tab->Flags & ImGuiTabItemFlags_Trailing) - tab->Offset -= scrolling_buttons_width + 1.0f; - } return tab_to_scroll_to; } @@ -7599,9 +7649,9 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImRect bb(pos, pos + size); // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) - // If TabBar fitting policy is scroll, then just clip through the BarRect - float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->WidthLeading; - float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->WidthTrailing; // Leading buttons will be clipped by BarRect.Max.x, trailing buttons will be clipped at BarRect.Minx + LeadingsWidth + // Leading buttons will be clipped by BarRect.Max.x, Trailing buttons will be clipped at BarRect.Min.x + LeadingsWidth (+ spacing if there are some buttons), and central tabs will be clipped inbetween + float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->TrailingSection.Width + tab_bar->CentralSection.InnerSpacing; + float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->LeadingSection.Width + tab_bar->LeadingSection.InnerSpacing; bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x + offset_trailing > tab_bar->BarRect.Max.x); if (want_clip_rect) PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x + offset_leading), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x - offset_trailing, bb.Max.y), true); From 5e5f25e2dd2aa278ce799e7c012017643c6fcd61 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 3 Sep 2020 12:49:00 +0200 Subject: [PATCH 23/28] Tab Bar: Rename named sections members into array. Various tidying up. (#3291) --- imgui.cpp | 8 +-- imgui_demo.cpp | 2 +- imgui_internal.h | 18 +++--- imgui_widgets.cpp | 147 +++++++++++++++++++++------------------------- 4 files changed, 79 insertions(+), 96 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4518cd80..1d65396a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10563,10 +10563,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImDrawList* draw_list = ImGui::GetForegroundDrawList(); draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); - if (tab_bar->LeadingSection.Width > 0.0f) - draw_list->AddLine(ImVec2(tab_bar->BarRect.Min.x + tab_bar->LeadingSection.Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Min.x + tab_bar->LeadingSection.Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); - if (tab_bar->TrailingSection.Width > 0.0f) - draw_list->AddLine(ImVec2(tab_bar->BarRect.Max.x - tab_bar->TrailingSection.Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x - tab_bar->TrailingSection.Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + if (tab_bar->Sections[0].Width > 0.0f) + draw_list->AddLine(ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + if (tab_bar->Sections[2].Width > 0.0f) + draw_list->AddLine(ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); } if (open) { diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3cc153cb..e1d31063 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2363,7 +2363,7 @@ static void ShowDemoWindowLayout() for (int i = 0; i < 3; i++) active_tabs.push_back(next_tab_id++); - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); diff --git a/imgui_internal.h b/imgui_internal.h index bb633253..e61fbed7 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1725,14 +1725,14 @@ struct ImGuiTabItem ImGuiTabItem() { ID = 0; Flags = ImGuiTabItemFlags_None; LastFrameVisible = LastFrameSelected = -1; NameOffset = -1; Offset = Width = ContentWidth = 0.0f; BeginOrder = -1; IndexDuringLayout = -1; WantClose = false; } }; -struct TabBarLayoutSection +struct ImGuiTabBarSection { - ImGuiTabItem* Tabs; // Pointer to the first tab_bar->Tabs matching the section - int TabCount; - float Width; - float WidthIdeal; - float InnerSpacing; // Horizontal ItemInnerSpacing, used by Leading/Trailing section, to correctly offset from Central section - TabBarLayoutSection() { Tabs = NULL; TabCount = 0; Width = 0.0f; WidthIdeal = 0.0f; InnerSpacing = 0.0f; } + int TabStartIndex; + int TabCount; + float Width; + float WidthIdeal; + float InnerSpacing; // Horizontal ItemInnerSpacing, used by Leading/Trailing section, to correctly offset from Central section + ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); } }; // Storage for a tab bar (sizeof() 92~96 bytes) @@ -1757,9 +1757,7 @@ struct ImGuiTabBar ImGuiID ReorderRequestTabId; ImS8 ReorderRequestDir; ImS8 TabsActiveCount; // Number of tabs submitted this frame. - TabBarLayoutSection LeadingSection; - TabBarLayoutSection CentralSection; - TabBarLayoutSection TrailingSection; + ImGuiTabBarSection Sections[3]; bool WantLayout; bool VisibleTabWasSubmitted; short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 0c81dd0b..3628b161 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6786,7 +6786,6 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - EndTabBar() // - TabBarLayout() [Internal] // - TabBarLayoutComputeTabsWidth() [Internal] -// - TabBarLayoutActiveTabsForSection() [Internal] // - TabBarCalcTabID() [Internal] // - TabBarCalcMaxTabWidth() [Internal] // - TabBarFindTabById() [Internal] @@ -6803,7 +6802,6 @@ namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); static void TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width); - static float TabBarLayoutActiveTabsForSection(ImGuiTabBar* tab_bar, TabBarLayoutSection* section, float next_tab_offset); static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); @@ -6828,14 +6826,14 @@ ImGuiTabBar::ImGuiTabBar() LastTabItemIdx = -1; } -static int IMGUI_CDECL TabItemComparerByPositionAndIndex(const void* lhs, const void* rhs) +static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; - const int a_position = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; - const int b_position = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; - if (a_position != b_position) - return a_position - b_position; + const int a_section = (a->Flags & ImGuiTabItemFlags_Leading) ? 0 : (a->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + const int b_section = (b->Flags & ImGuiTabItemFlags_Leading) ? 0 : (b->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + if (a_section != b_section) + return a_section - b_section; return (int)(a->IndexDuringLayout - b->IndexDuringLayout); } @@ -6972,7 +6970,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Garbage collect by compacting list int tab_dst_n = 0; bool need_sort_trailing_or_leading = false; - tab_bar->LeadingSection.TabCount = tab_bar->CentralSection.TabCount = tab_bar->TrailingSection.TabCount = 0; + tab_bar->Sections[0].TabCount = tab_bar->Sections[1].TabCount = tab_bar->Sections[2].TabCount = 0; for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; @@ -6987,20 +6985,19 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (tab_dst_n != tab_src_n) tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; - tab_bar->Tabs[tab_dst_n].IndexDuringLayout = (ImS8)tab_dst_n; - bool is_leading = (tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Leading) != 0; - bool is_trailing = (tab_bar->Tabs[tab_dst_n].Flags & ImGuiTabItemFlags_Trailing) != 0; - if (tab_dst_n > 0 && is_leading && !(tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Leading)) + tab = &tab_bar->Tabs[tab_dst_n]; + tab->IndexDuringLayout = (ImS8)tab_dst_n; + + ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; + int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + + if (tab_dst_n > 0 && curr_tab_section_n == 0 && prev_tab_section_n != 0) need_sort_trailing_or_leading = true; - if (tab_dst_n > 0 && (tab_bar->Tabs[tab_dst_n - 1].Flags & ImGuiTabItemFlags_Trailing) && !is_trailing) + if (tab_dst_n > 0 && prev_tab_section_n == 2 && curr_tab_section_n != 2) need_sort_trailing_or_leading = true; - if (is_leading) - tab_bar->LeadingSection.TabCount++; - else if (is_trailing) - tab_bar->TrailingSection.TabCount++; - else - tab_bar->CentralSection.TabCount++; + tab_bar->Sections[curr_tab_section_n].TabCount++; tab_dst_n++; } @@ -7008,11 +7005,11 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->Tabs.resize(tab_dst_n); if (need_sort_trailing_or_leading) - ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByPositionAndIndex); + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); - tab_bar->LeadingSection.Tabs = tab_bar->Tabs.Data; - tab_bar->CentralSection.Tabs = tab_bar->Tabs.Data + tab_bar->LeadingSection.TabCount; - tab_bar->TrailingSection.Tabs = tab_bar->Tabs.Data + tab_bar->LeadingSection.TabCount + tab_bar->CentralSection.TabCount; + tab_bar->Sections[0].TabStartIndex = 0; + tab_bar->Sections[1].TabStartIndex = tab_bar->Sections[0].TabStartIndex + tab_bar->Sections[0].TabCount; + tab_bar->Sections[2].TabStartIndex = tab_bar->Sections[1].TabStartIndex + tab_bar->Sections[1].TabCount; // Setup next selected tab ImGuiID scroll_track_selected_tab_id = 0; @@ -7042,7 +7039,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); // Compute ideal widths - tab_bar->LeadingSection.Width = tab_bar->CentralSection.Width = tab_bar->TrailingSection.Width = 0.0f; + tab_bar->Sections[0].Width = tab_bar->Sections[1].Width = tab_bar->Sections[2].Width = 0.0f; const float tab_max_width = TabBarCalcMaxTabWidth(); ImGuiTabItem* most_recently_selected_tab = NULL; @@ -7066,23 +7063,18 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; - float width = ImMin(tab->ContentWidth, tab_max_width); - if (tab->Flags & ImGuiTabItemFlags_Leading) - tab_bar->LeadingSection.Width += width + (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); - else if (tab->Flags & ImGuiTabItemFlags_Trailing) - tab_bar->TrailingSection.Width += width + (tab_n > tab_bar->LeadingSection.TabCount + tab_bar->CentralSection.TabCount ? g.Style.ItemInnerSpacing.x : 0.0f); - else - tab_bar->CentralSection.Width += width + (tab_n > tab_bar->LeadingSection.TabCount ? g.Style.ItemInnerSpacing.x : 0.0f); + int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + ImGuiTabBarSection* section = &tab_bar->Sections[section_n]; + float width = ImMin(tab->ContentWidth, tab_max_width) + (tab_n > section->TabStartIndex) ? g.Style.ItemInnerSpacing.x : 0.0f; + section->Width = section->WidthIdeal = section->Width + width; - g.ShrinkWidthBuffer[tab_n].Index = tab_n; // Store data so we can build an array sorted by width if we need to shrink tabs down + // Store data so we can build an array sorted by width if we need to shrink tabs down + g.ShrinkWidthBuffer[tab_n].Index = tab_n; g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth; IM_ASSERT(width > 0.0f); tab->Width = width; } - tab_bar->LeadingSection.WidthIdeal = tab_bar->LeadingSection.Width; - tab_bar->CentralSection.WidthIdeal = tab_bar->CentralSection.Width; - tab_bar->TrailingSection.WidthIdeal = tab_bar->TrailingSection.Width; // We want to know here if we'll need the scrolling buttons, to adjust available width with resizable leading/trailing bool scrolling_buttons = (tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); @@ -7090,16 +7082,25 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) TabBarLayoutComputeTabsWidth(tab_bar, scrolling_buttons, scrolling_buttons_width); // Layout all active tabs - tab_bar->WidthAllTabs = tab_bar->WidthAllTabsIdeal = 0.0f; float next_tab_offset = 0.0f; + tab_bar->WidthAllTabs = tab_bar->WidthAllTabsIdeal = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) + { + // TabBarScrollingButtons will alter BarRect.Max.x, so we need to anticipate BarRect width reduction + ImGuiTabBarSection* section = &tab_bar->Sections[section_n]; + if (section_n == 2) + next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - section->Width - (scrolling_buttons ? scrolling_buttons_width + 1.0f : 0.0f), next_tab_offset); - // TabBarLayoutActiveTabsForSection will also alter tab_bar->WidthAllTabs and WidthAllTabsIdeal - next_tab_offset = TabBarLayoutActiveTabsForSection(tab_bar, &tab_bar->LeadingSection, next_tab_offset); // Leading Section - next_tab_offset = TabBarLayoutActiveTabsForSection(tab_bar, &tab_bar->CentralSection, next_tab_offset); // Central Section - - // TabBarScrollingButtons will alter BarRect.Max.x, so we need to anticipate BarRect width reduction - next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - tab_bar->TrailingSection.Width - (scrolling_buttons ? scrolling_buttons_width + 1.0f : 0.0f), next_tab_offset); - TabBarLayoutActiveTabsForSection(tab_bar, &tab_bar->TrailingSection, next_tab_offset); // Trailing Section + for (int tab_n = 0; tab_n < section->TabCount; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[section->TabStartIndex + tab_n]; + tab->Offset = next_tab_offset; + next_tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + } + tab_bar->WidthAllTabs += ImMax(section->Width + section->InnerSpacing, 0.0f); + tab_bar->WidthAllTabsIdeal += ImMax(section->WidthIdeal + section->InnerSpacing, 0.0f); + next_tab_offset += section->InnerSpacing; + } // Horizontal scrolling buttons if (scrolling_buttons) @@ -7153,8 +7154,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) #ifdef IMGUI_ENABLE_TEST_ENGINE if (g.IO.KeyAlt) { - window->DrawList->AddRect(tab_bar->BarRect.Min, ImVec2(tab_bar->BarRect.Min.x + tab_bar->LeadingSection.Width, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); - window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - tab_bar->TrailingSection.Width, tab_bar->BarRect.Min.y), tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); + window->DrawList->AddRect(tab_bar->BarRect.Min, ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); + window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Min.y), tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); } #endif } @@ -7164,26 +7165,26 @@ static void ImGui::TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrol ImGuiContext& g = *GImGui; // Compute Leading/Trailing relative additional horizontal inner spacing - float leading_trailing_common_inner_space = (tab_bar->LeadingSection.TabCount > 0 && tab_bar->TrailingSection.TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); - bool resizing_leading_trailing_only = (tab_bar->LeadingSection.Width + tab_bar->TrailingSection.Width + leading_trailing_common_inner_space) > (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)); + float leading_trailing_common_inner_space = (tab_bar->Sections[0].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); + bool resizing_leading_trailing_only = (tab_bar->Sections[0].Width + tab_bar->Sections[2].Width + leading_trailing_common_inner_space) > (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)); - tab_bar->LeadingSection.InnerSpacing = tab_bar->LeadingSection.TabCount > 0 && (tab_bar->CentralSection.TabCount + tab_bar->TrailingSection.TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - tab_bar->CentralSection.InnerSpacing = tab_bar->CentralSection.TabCount > 0 && tab_bar->TrailingSection.TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - tab_bar->TrailingSection.InnerSpacing = 0.0f; + tab_bar->Sections[0].InnerSpacing = tab_bar->Sections[0].TabCount > 0 && (tab_bar->Sections[1].TabCount + tab_bar->Sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + tab_bar->Sections[1].InnerSpacing = tab_bar->Sections[1].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + tab_bar->Sections[2].InnerSpacing = 0.0f; // Compute width float width_excess = resizing_leading_trailing_only - ? (tab_bar->LeadingSection.Width + tab_bar->TrailingSection.Width + leading_trailing_common_inner_space) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)) - : ImMax(tab_bar->CentralSection.Width + tab_bar->CentralSection.InnerSpacing - (tab_bar->BarRect.GetWidth() - tab_bar->LeadingSection.Width - tab_bar->LeadingSection.InnerSpacing - tab_bar->TrailingSection.Width - tab_bar->TrailingSection.InnerSpacing), 0.0f); + ? (tab_bar->Sections[0].Width + tab_bar->Sections[2].Width + leading_trailing_common_inner_space) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)) + : ImMax(tab_bar->Sections[1].Width + tab_bar->Sections[1].InnerSpacing - (tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].Width - tab_bar->Sections[0].InnerSpacing - tab_bar->Sections[2].Width - tab_bar->Sections[2].InnerSpacing), 0.0f); if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || resizing_leading_trailing_only) { // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data if (resizing_leading_trailing_only) - memmove(g.ShrinkWidthBuffer.Data + tab_bar->LeadingSection.TabCount, g.ShrinkWidthBuffer.Data + tab_bar->LeadingSection.TabCount + tab_bar->CentralSection.TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->TrailingSection.TabCount); + memmove(g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount + tab_bar->Sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[2].TabCount); else - memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->LeadingSection.TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->CentralSection.TabCount); - int tab_n_shrinkable = (resizing_leading_trailing_only ? tab_bar->LeadingSection.TabCount + tab_bar->TrailingSection.TabCount : tab_bar->CentralSection.TabCount); + memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[1].TabCount); + int tab_n_shrinkable = (resizing_leading_trailing_only ? tab_bar->Sections[0].TabCount + tab_bar->Sections[2].TabCount : tab_bar->Sections[1].TabCount); ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); @@ -7205,32 +7206,16 @@ static void ImGui::TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrol if (resizing_leading_trailing_only) { - tab_bar->LeadingSection.Width -= leading_excess; - tab_bar->TrailingSection.Width -= trailing_excess; + tab_bar->Sections[0].Width -= leading_excess; + tab_bar->Sections[2].Width -= trailing_excess; } else { - tab_bar->CentralSection.Width -= width_excess; + tab_bar->Sections[1].Width -= width_excess; } } } -static float ImGui::TabBarLayoutActiveTabsForSection(ImGuiTabBar* tab_bar, TabBarLayoutSection* section, float next_tab_offset) -{ - ImGuiContext& g = *GImGui; - - for(int i = 0; i < section->TabCount; ++i) - { - ImGuiTabItem* tab = section->Tabs + i; - tab->Offset = next_tab_offset; - next_tab_offset += tab->Width + (i < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); - } - tab_bar->WidthAllTabs += ImMax(section->Width + section->InnerSpacing, 0.0f); - tab_bar->WidthAllTabsIdeal += ImMax(section->WidthIdeal + section->InnerSpacing, 0.0f); - - return next_tab_offset + section->InnerSpacing; -} - // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label) { @@ -7311,11 +7296,11 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) int order = tab_bar->GetTabOrder(tab); // Scrolling happens only in the central section (leading/trailing sections are not scrolling) - float scrollable_width = tab_bar->BarRect.GetWidth() - tab_bar->LeadingSection.Width - tab_bar->TrailingSection.Width - tab_bar->CentralSection.InnerSpacing; + float scrollable_width = tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].Width - tab_bar->Sections[2].Width - tab_bar->Sections[1].InnerSpacing; - // We make all tabs positions all relative LeadingSection.Width to make code simpler - float tab_x1 = tab->Offset - tab_bar->LeadingSection.Width + (order > tab_bar->LeadingSection.TabCount - 1 ? -margin : 0.0f); - float tab_x2 = tab->Offset - tab_bar->LeadingSection.Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - tab_bar->TrailingSection.TabCount ? margin : 1.0f); + // We make all tabs positions all relative Sections[0].Width to make code simpler + float tab_x1 = tab->Offset - tab_bar->Sections[0].Width + (order > tab_bar->Sections[0].TabCount - 1 ? -margin : 0.0f); + float tab_x2 = tab->Offset - tab_bar->Sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - tab_bar->Sections[2].TabCount ? margin : 1.0f); tab_bar->ScrollingTargetDistToVisibility = 0.0f; if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) { @@ -7650,9 +7635,9 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) // Leading buttons will be clipped by BarRect.Max.x, Trailing buttons will be clipped at BarRect.Min.x + LeadingsWidth (+ spacing if there are some buttons), and central tabs will be clipped inbetween - float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->TrailingSection.Width + tab_bar->CentralSection.InnerSpacing; - float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->LeadingSection.Width + tab_bar->LeadingSection.InnerSpacing; - bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x + offset_trailing > tab_bar->BarRect.Max.x); + float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->Sections[2].Width + tab_bar->Sections[1].InnerSpacing; + float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->Sections[0].Width + tab_bar->Sections[0].InnerSpacing; + bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x > tab_bar->BarRect.Max.x - offset_trailing); if (want_clip_rect) PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x + offset_leading), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x - offset_trailing, bb.Max.y), true); From 3422cb1308e70347f04b59e79b93e9cad7cd81b6 Mon Sep 17 00:00:00 2001 From: Louis Schnellbach Date: Fri, 4 Sep 2020 11:13:46 +0200 Subject: [PATCH 24/28] Tab Bar: Various fixes. Tried to reduce code complexity. (#3291) --- docs/CHANGELOG.txt | 16 +++--- imgui.h | 4 +- imgui_internal.h | 4 +- imgui_widgets.cpp | 128 ++++++++++++++++++--------------------------- 4 files changed, 64 insertions(+), 88 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d1f5fb2a..4be7d31d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -65,9 +65,9 @@ Other Changes: - InputText: Fixed minor scrolling glitch when erasing trailing lines in InputTextMultiline(). - InputText: Fixed cursor being partially covered after using Ctrl+End key. - InputText: Fixed callback's helper DeleteChars() function when cursor is inside the deleted block. (#3454) -- InputText: Made pressing Down arrow on the last line when it doesn't have a carriage return not move to the end - of the line (so it is consistent with Up arrow, and behave same as Notepad and Visual Studio. Note that some - other text editors instead would move the crusor to the end of the line). [@Xipiryon] +- InputText: Made pressing Down arrow on the last line when it doesn't have a carriage return not move to + the end of the line (so it is consistent with Up arrow, and behave same as Notepad and Visual Studio. + Note that some other text editors instead would move the crusor to the end of the line). [@Xipiryon] - DragFloat, DragScalar: Fixed ImGuiSliderFlags_ClampOnInput not being honored in the special case where v_min == v_max. (#3361) - SliderInt, SliderScalar: Fixed reaching of maximum value with inverted integer min/max ranges, both @@ -80,17 +80,17 @@ Other Changes: rather than the Mouse Down+Up sequence, even if the _OpenOnArrow flag isn't set. This is standard behavior and amends the change done in 1.76 which only affected cases were _OpenOnArrow flag was set. (This is also necessary to support full multi/range-select/drag and drop operations.) +- Tab Bar: Added TabItemButton() to submit tab that behave like a button. (#3291) [@Xipiryon] +- Tab Bar: Added ImGuiTabItemFlags_Leading and ImGuiTabItemFlags_Trailing flags to position tabs or button + at either end of the tab bar. Those tabs won't be part of the scrolling region, and when reordering cannot + be moving outside of their section. Most often used with TabItemButton(). (#3291) [@Xipiryon] +- Tab Bar: Added ImGuiTabItemFlags_NoReorder flag to disable reordering a given tab. - Tab Bar: Keep tab item close button visible while dragging a tab (independent of hovering state). - Tab Bar: Fixed a small bug where closing a tab that is not selected would leave a tab hole for a frame. - Tab Bar: Fixed a small bug where scrolling buttons (with ImGuiTabBarFlags_FittingPolicyScroll) would generate an unnecessary extra draw call. - Tab Bar: Fixed a small bug where toggling a tab bar from Reorderable to not Reorderable would leave tabs reordered in the tab list popup. [@Xipiryon] -- Tab Bar: Added TabItemButton() to submit tab that behave like a button. (#3291) [@Xipiryon] -- Tab Bar: Added ImGuiTabItemFlags_Leading and ImGuiTabItemFlags_Trailing flags to position tabs or button at - either end of the tab bar. Those tabs won't be part of the scrolling region, won't shrink down, and when - reordering cannot be moving outside of their section. Most often used with TabItemButton(). (#3291) [@Xipiryon] -- Tab Bar: Added ImGuiTabItemFlags_NoReorder flag to disable reordering a given tab. - Columns: Fix inverted ClipRect being passed to renderer when using certain primitives inside of a fully clipped column. (#3475) [@szreder] - Popups, Tooltips: Fix edge cases issues with positionning popups and tooltips when they are larger than diff --git a/imgui.h b/imgui.h index 91a0b162..c7234346 100644 --- a/imgui.h +++ b/imgui.h @@ -957,8 +957,8 @@ enum ImGuiTabItemFlags_ ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() ImGuiTabItemFlags_NoTooltip = 1 << 4, // Disable tooltip for the given tab ImGuiTabItemFlags_NoReorder = 1 << 5, // Disable reordering this tab or having another tab cross over this tab - ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button) and disable resizing down - ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) and disable resizing down + ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button) + ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) }; // Flags for ImGui::IsWindowFocused() diff --git a/imgui_internal.h b/imgui_internal.h index e61fbed7..796e03e5 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1704,7 +1704,7 @@ enum ImGuiTabBarFlagsPrivate_ enum ImGuiTabItemFlagsPrivate_ { ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) - ImGuiTabItemFlags_Button = 1 << 21 // [Internal] Used by TabItemButton, change the tab item behavior to mimic a button + ImGuiTabItemFlags_Button = 1 << 21 // Used by TabItemButton, change the tab item behavior to mimic a button }; // Storage for one active tab item (sizeof() 28~32 bytes) @@ -1732,6 +1732,7 @@ struct ImGuiTabBarSection float Width; float WidthIdeal; float InnerSpacing; // Horizontal ItemInnerSpacing, used by Leading/Trailing section, to correctly offset from Central section + float WidthWithSpacing() const { return Width + InnerSpacing; } ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); } }; @@ -1761,6 +1762,7 @@ struct ImGuiTabBar bool WantLayout; bool VisibleTabWasSubmitted; short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() + bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame ImVec2 FramePadding; // style.FramePadding locked at the time of BeginTabBar() ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3628b161..2ddbbb88 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6785,7 +6785,6 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - BeginTabBarEx() [Internal] // - EndTabBar() // - TabBarLayout() [Internal] -// - TabBarLayoutComputeTabsWidth() [Internal] // - TabBarCalcTabID() [Internal] // - TabBarCalcMaxTabWidth() [Internal] // - TabBarFindTabById() [Internal] @@ -6801,7 +6800,6 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); - static void TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width); static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); @@ -6824,6 +6822,7 @@ ImGuiTabBar::ImGuiTabBar() TabsActiveCount = 0; WantLayout = VisibleTabWasSubmitted = false; LastTabItemIdx = -1; + TabsAddedNew = false; } static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) @@ -6899,9 +6898,11 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG return true; } - // When toggling ImGuiTabBarFlags_Reorderable flag, ensure tabs are ordered based on their submission order. - if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1) - ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); + // When toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable, ensure tabs are ordered based on their submission order. + if (((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) || tab_bar->TabsAddedNew) + if (tab_bar->Tabs.Size > 1) + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); + tab_bar->TabsAddedNew = false; // Flags if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) @@ -6992,6 +6993,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + // We will need sorting if either current tab is leading (section_n == 0), but not the previous one, + // or if the current is not trailing (section_n == 2), but the previous one is. if (tab_dst_n > 0 && curr_tab_section_n == 0 && prev_tab_section_n != 0) need_sort_trailing_or_leading = true; if (tab_dst_n > 0 && prev_tab_section_n == 2 && curr_tab_section_n != 2) @@ -7011,6 +7014,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->Sections[1].TabStartIndex = tab_bar->Sections[0].TabStartIndex + tab_bar->Sections[0].TabCount; tab_bar->Sections[2].TabStartIndex = tab_bar->Sections[1].TabStartIndex + tab_bar->Sections[1].TabCount; + tab_bar->Sections[0].InnerSpacing = tab_bar->Sections[0].TabCount > 0 && (tab_bar->Sections[1].TabCount + tab_bar->Sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + tab_bar->Sections[1].InnerSpacing = tab_bar->Sections[1].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + tab_bar->Sections[2].InnerSpacing = 0.0f; + // Setup next selected tab ImGuiID scroll_track_selected_tab_id = 0; if (tab_bar->NextSelectedTabId) @@ -7040,6 +7047,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Compute ideal widths tab_bar->Sections[0].Width = tab_bar->Sections[1].Width = tab_bar->Sections[2].Width = 0.0f; + tab_bar->Sections[0].WidthIdeal = tab_bar->Sections[1].WidthIdeal = tab_bar->Sections[2].WidthIdeal = 0.0f; const float tab_max_width = TabBarCalcMaxTabWidth(); ImGuiTabItem* most_recently_selected_tab = NULL; @@ -7065,8 +7073,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; ImGuiTabBarSection* section = &tab_bar->Sections[section_n]; - float width = ImMin(tab->ContentWidth, tab_max_width) + (tab_n > section->TabStartIndex) ? g.Style.ItemInnerSpacing.x : 0.0f; - section->Width = section->WidthIdeal = section->Width + width; + float width = ImMin(tab->ContentWidth, tab_max_width); + section->Width = section->WidthIdeal = section->Width + width + (tab_n > section->TabStartIndex ? g.Style.ItemInnerSpacing.x : 0.0f); // Store data so we can build an array sorted by width if we need to shrink tabs down g.ShrinkWidthBuffer[tab_n].Index = tab_n; @@ -7076,14 +7084,46 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab->Width = width; } + tab_bar->WidthAllTabsIdeal = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) + tab_bar->WidthAllTabsIdeal += tab_bar->Sections[section_n].WidthIdeal + tab_bar->Sections[section_n].InnerSpacing; + // We want to know here if we'll need the scrolling buttons, to adjust available width with resizable leading/trailing bool scrolling_buttons = (tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); float scrolling_buttons_width = GetTabBarScrollingButtonSize().x * 2.0f; - TabBarLayoutComputeTabsWidth(tab_bar, scrolling_buttons, scrolling_buttons_width); + + // Compute width + bool central_section_is_visible = tab_bar->Sections[0].WidthWithSpacing() + tab_bar->Sections[2].WidthWithSpacing() < tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f); + float width_excess = central_section_is_visible + ? ImMax(tab_bar->Sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].WidthWithSpacing() - tab_bar->Sections[2].WidthWithSpacing()), 0.0f) + : (tab_bar->Sections[0].WidthWithSpacing() + tab_bar->Sections[2].WidthWithSpacing()) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)); + + if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown || !central_section_is_visible)) + { + // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data + if (central_section_is_visible) + memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[1].TabCount); // Move central section tabs + else + memmove(g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount + tab_bar->Sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[2].TabCount); // Replace central section tabs with trailing + int tab_n_shrinkable = (central_section_is_visible ? tab_bar->Sections[1].TabCount : tab_bar->Sections[0].TabCount + tab_bar->Sections[2].TabCount); + + ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); + + // Update each section width with shrink values + for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) + { + float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); + ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; + int section_n = tab->Flags & ImGuiTabItemFlags_Leading ? 0 : tab->Flags & ImGuiTabItemFlags_Trailing ? 2 : 1; + + tab_bar->Sections[section_n].Width -= (tab->Width - shrinked_width); + tab->Width = shrinked_width; + } + } // Layout all active tabs float next_tab_offset = 0.0f; - tab_bar->WidthAllTabs = tab_bar->WidthAllTabsIdeal = 0.0f; + tab_bar->WidthAllTabs = 0.0f; for (int section_n = 0; section_n < 3; section_n++) { // TabBarScrollingButtons will alter BarRect.Max.x, so we need to anticipate BarRect width reduction @@ -7097,8 +7137,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab->Offset = next_tab_offset; next_tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } - tab_bar->WidthAllTabs += ImMax(section->Width + section->InnerSpacing, 0.0f); - tab_bar->WidthAllTabsIdeal += ImMax(section->WidthIdeal + section->InnerSpacing, 0.0f); + tab_bar->WidthAllTabs += ImMax(section->WidthWithSpacing(), 0.0f); next_tab_offset += section->InnerSpacing; } @@ -7150,70 +7189,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; ItemSize(ImVec2(tab_bar->WidthAllTabsIdeal, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); - -#ifdef IMGUI_ENABLE_TEST_ENGINE - if (g.IO.KeyAlt) - { - window->DrawList->AddRect(tab_bar->BarRect.Min, ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); - window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Min.y), tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); - } -#endif -} - -static void ImGui::TabBarLayoutComputeTabsWidth(ImGuiTabBar* tab_bar, bool scrolling_buttons, float scrolling_buttons_width) -{ - ImGuiContext& g = *GImGui; - - // Compute Leading/Trailing relative additional horizontal inner spacing - float leading_trailing_common_inner_space = (tab_bar->Sections[0].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f); - bool resizing_leading_trailing_only = (tab_bar->Sections[0].Width + tab_bar->Sections[2].Width + leading_trailing_common_inner_space) > (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)); - - tab_bar->Sections[0].InnerSpacing = tab_bar->Sections[0].TabCount > 0 && (tab_bar->Sections[1].TabCount + tab_bar->Sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - tab_bar->Sections[1].InnerSpacing = tab_bar->Sections[1].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - tab_bar->Sections[2].InnerSpacing = 0.0f; - - // Compute width - float width_excess = resizing_leading_trailing_only - ? (tab_bar->Sections[0].Width + tab_bar->Sections[2].Width + leading_trailing_common_inner_space) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)) - : ImMax(tab_bar->Sections[1].Width + tab_bar->Sections[1].InnerSpacing - (tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].Width - tab_bar->Sections[0].InnerSpacing - tab_bar->Sections[2].Width - tab_bar->Sections[2].InnerSpacing), 0.0f); - - if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || resizing_leading_trailing_only) - { - // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data - if (resizing_leading_trailing_only) - memmove(g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount + tab_bar->Sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[2].TabCount); - else - memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[1].TabCount); - int tab_n_shrinkable = (resizing_leading_trailing_only ? tab_bar->Sections[0].TabCount + tab_bar->Sections[2].TabCount : tab_bar->Sections[1].TabCount); - - ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); - - // Total Leading and Trailing shrink values can be different, we need to keep track of how much each section was shrinked - float leading_excess = 0.0f; - float trailing_excess = 0.0f; - for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) - { - float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); - ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; - - if (tab->Flags & ImGuiTabItemFlags_Leading) - leading_excess += (tab->Width - shrinked_width); - else if (tab->Flags & ImGuiTabItemFlags_Trailing) - trailing_excess += (tab->Width - shrinked_width); - - tab->Width = shrinked_width; - } - - if (resizing_leading_trailing_only) - { - tab_bar->Sections[0].Width -= leading_excess; - tab_bar->Sections[2].Width -= trailing_excess; - } - else - { - tab_bar->Sections[1].Width -= width_excess; - } - } } // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. @@ -7407,7 +7382,6 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) } } window->DC.CursorPos = backup_cursor_pos; - tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; return tab_to_scroll_to; @@ -7569,7 +7543,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab = &tab_bar->Tabs.back(); tab->ID = id; tab->Width = size.x; - tab_is_new = true; + tab_bar->TabsAddedNew = tab_is_new = true; } tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab); tab->ContentWidth = size.x; From 205874f5b15a3689d253c6c149c62ad704dae0ab Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 21 Sep 2020 18:40:18 +0200 Subject: [PATCH 25/28] Tab Bar: Fix reorderable tab bars. Fix misleading use of tab_max_width in TabBarLayout(). Misc amends, shortening. (#3291) --- imgui.h | 2 +- imgui_demo.cpp | 31 +++++++------ imgui_internal.h | 18 ++++---- imgui_widgets.cpp | 113 +++++++++++++++++++++++----------------------- 4 files changed, 84 insertions(+), 80 deletions(-) diff --git a/imgui.h b/imgui.h index c7234346..4fc23e83 100644 --- a/imgui.h +++ b/imgui.h @@ -655,7 +655,7 @@ namespace ImGui IMGUI_API void EndTabBar(); // only call EndTabBar() if BeginTabBar() returns true! IMGUI_API bool BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0); // create a Tab. Returns true if the Tab is selected. IMGUI_API void EndTabItem(); // only call EndTabItem() if BeginTabItem() returns true! - IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button + IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar. IMGUI_API void SetTabItemClosed(const char* tab_or_docked_window_label); // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name. // Logging/Capture diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e1d31063..c728bbe1 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2363,6 +2363,15 @@ static void ShowDemoWindowLayout() for (int i = 0; i < 3; i++) active_tabs.push_back(next_tab_id++); + // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together. + // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags... + // but they tend to make more sense together) + static bool show_leading_button = true; + static bool show_trailing_button = true; + ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); + ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); + + // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) @@ -2370,18 +2379,12 @@ static void ShowDemoWindowLayout() if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); - static bool show_leading_button = true; - static bool show_trailing_button = true; - ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); - ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); - if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { - // Demo Leading Tabs: click the "?" button to open a menu - // Note that it is possible to submit regular non-button tabs with Leading/Trailing flags, - // or Button without Leading/Trailing flags... but they tend to make more sense together. - if (show_leading_button && ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) - ImGui::OpenPopup("MyHelpMenu"); + // Demo a Leading TabItemButton(): click the "?" button to open a menu + if (show_leading_button) + if (ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) + ImGui::OpenPopup("MyHelpMenu"); if (ImGui::BeginPopup("MyHelpMenu")) { ImGui::Selectable("Hello!"); @@ -2389,8 +2392,10 @@ static void ShowDemoWindowLayout() } // Demo Trailing Tabs: click the "+" button to add a new tab (in your app you may want to use a font icon instead of the "+") - if (show_trailing_button && ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) - active_tabs.push_back(next_tab_id++); + // Note that we submit it before the regular tabs, but because of the ImGuiTabItemFlags_Trailing flag it will always appear at the end. + if (show_trailing_button) + if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) + active_tabs.push_back(next_tab_id++); // Add new tab // Submit our regular tabs for (int n = 0; n < active_tabs.Size; ) @@ -2411,7 +2416,7 @@ static void ShowDemoWindowLayout() } ImGui::EndTabBar(); - } + } ImGui::Separator(); ImGui::TreePop(); } diff --git a/imgui_internal.h b/imgui_internal.h index 796e03e5..b48e2cc4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1716,7 +1716,7 @@ struct ImGuiTabItem int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance float Offset; // Position relative to beginning of tab float Width; // Width currently displayed - float ContentWidth; // Width of actual contents, stored during BeginTabItem() call + float ContentWidth; // Width of label, stored during BeginTabItem() call ImS16 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS8 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable ImS8 IndexDuringLayout; // Index only used during TabBarLayout() @@ -1727,13 +1727,13 @@ struct ImGuiTabItem struct ImGuiTabBarSection { - int TabStartIndex; - int TabCount; - float Width; - float WidthIdeal; - float InnerSpacing; // Horizontal ItemInnerSpacing, used by Leading/Trailing section, to correctly offset from Central section - float WidthWithSpacing() const { return Width + InnerSpacing; } + int TabStartIndex; // Index of first tab in this section. + int TabCount; // Number of tabs in this section. + float Width; // Width of this section (after shrinking down) + float Spacing; // Horizontal spacing at the end of the section. + ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); } + float WidthWithSpacing() const { return Width + Spacing; } }; // Storage for a tab bar (sizeof() 92~96 bytes) @@ -1758,12 +1758,12 @@ struct ImGuiTabBar ImGuiID ReorderRequestTabId; ImS8 ReorderRequestDir; ImS8 TabsActiveCount; // Number of tabs submitted this frame. - ImGuiTabBarSection Sections[3]; bool WantLayout; bool VisibleTabWasSubmitted; - short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame + short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() ImVec2 FramePadding; // style.FramePadding locked at the time of BeginTabBar() + ImGuiTabBarSection Sections[3]; // Layout sections: Leading, Central, Trailing ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer. ImGuiTabBar(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2ddbbb88..e224859a 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6820,9 +6820,8 @@ ImGuiTabBar::ImGuiTabBar() ReorderRequestTabId = 0; ReorderRequestDir = 0; TabsActiveCount = 0; - WantLayout = VisibleTabWasSubmitted = false; + WantLayout = VisibleTabWasSubmitted = TabsAddedNew = false; LastTabItemIdx = -1; - TabsAddedNew = false; } static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) @@ -6898,8 +6897,8 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG return true; } - // When toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable, ensure tabs are ordered based on their submission order. - if (((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) || tab_bar->TabsAddedNew) + // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable + if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable))) if (tab_bar->Tabs.Size > 1) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); tab_bar->TabsAddedNew = false; @@ -6966,12 +6965,14 @@ void ImGui::EndTabBar() static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; + ImGuiTabBarSection* sections = tab_bar->Sections; tab_bar->WantLayout = false; // Garbage collect by compacting list + // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) int tab_dst_n = 0; - bool need_sort_trailing_or_leading = false; - tab_bar->Sections[0].TabCount = tab_bar->Sections[1].TabCount = tab_bar->Sections[2].TabCount = 0; + bool need_sort_by_section = false; + sections[0].TabCount = sections[1].TabCount = sections[2].TabCount = 0; for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; @@ -6989,34 +6990,34 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab = &tab_bar->Tabs[tab_dst_n]; tab->IndexDuringLayout = (ImS8)tab_dst_n; + // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another) ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; int curr_tab_section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; int prev_tab_section_n = (prev_tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (prev_tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; - - // We will need sorting if either current tab is leading (section_n == 0), but not the previous one, - // or if the current is not trailing (section_n == 2), but the previous one is. if (tab_dst_n > 0 && curr_tab_section_n == 0 && prev_tab_section_n != 0) - need_sort_trailing_or_leading = true; + need_sort_by_section = true; if (tab_dst_n > 0 && prev_tab_section_n == 2 && curr_tab_section_n != 2) - need_sort_trailing_or_leading = true; - - tab_bar->Sections[curr_tab_section_n].TabCount++; + need_sort_by_section = true; + sections[curr_tab_section_n].TabCount++; tab_dst_n++; } if (tab_bar->Tabs.Size != tab_dst_n) tab_bar->Tabs.resize(tab_dst_n); - if (need_sort_trailing_or_leading) + if (need_sort_by_section) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); - tab_bar->Sections[0].TabStartIndex = 0; - tab_bar->Sections[1].TabStartIndex = tab_bar->Sections[0].TabStartIndex + tab_bar->Sections[0].TabCount; - tab_bar->Sections[2].TabStartIndex = tab_bar->Sections[1].TabStartIndex + tab_bar->Sections[1].TabCount; - - tab_bar->Sections[0].InnerSpacing = tab_bar->Sections[0].TabCount > 0 && (tab_bar->Sections[1].TabCount + tab_bar->Sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - tab_bar->Sections[1].InnerSpacing = tab_bar->Sections[1].TabCount > 0 && tab_bar->Sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - tab_bar->Sections[2].InnerSpacing = 0.0f; + // Calculate spacing between sections + sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + sections[2].Spacing = 0.0f; + sections[0].TabStartIndex = 0; + sections[1].TabStartIndex = sections[0].TabStartIndex + sections[0].TabCount; + sections[2].TabStartIndex = sections[1].TabStartIndex + sections[1].TabCount; + sections[0].Width = 0.0f; + sections[1].Width = 0.0f; + sections[2].Width = 0.0f; // Setup next selected tab ImGuiID scroll_track_selected_tab_id = 0; @@ -7042,16 +7043,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; - // Reserve shrink buffer for maximum possible number of tabs - g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); - // Compute ideal widths - tab_bar->Sections[0].Width = tab_bar->Sections[1].Width = tab_bar->Sections[2].Width = 0.0f; - tab_bar->Sections[0].WidthIdeal = tab_bar->Sections[1].WidthIdeal = tab_bar->Sections[2].WidthIdeal = 0.0f; - const float tab_max_width = TabBarCalcMaxTabWidth(); - ImGuiTabItem* most_recently_selected_tab = NULL; bool found_selected_tab_id = false; + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; @@ -7072,51 +7067,52 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x; int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; - ImGuiTabBarSection* section = &tab_bar->Sections[section_n]; - float width = ImMin(tab->ContentWidth, tab_max_width); - section->Width = section->WidthIdeal = section->Width + width + (tab_n > section->TabStartIndex ? g.Style.ItemInnerSpacing.x : 0.0f); + ImGuiTabBarSection* section = §ions[section_n]; + section->Width += tab->ContentWidth + (tab_n > section->TabStartIndex ? g.Style.ItemInnerSpacing.x : 0.0f); // Store data so we can build an array sorted by width if we need to shrink tabs down g.ShrinkWidthBuffer[tab_n].Index = tab_n; g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth; - IM_ASSERT(width > 0.0f); - tab->Width = width; + IM_ASSERT(tab->ContentWidth > 0.0f); + tab->Width = tab->ContentWidth; } tab_bar->WidthAllTabsIdeal = 0.0f; for (int section_n = 0; section_n < 3; section_n++) - tab_bar->WidthAllTabsIdeal += tab_bar->Sections[section_n].WidthIdeal + tab_bar->Sections[section_n].InnerSpacing; + tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; // We want to know here if we'll need the scrolling buttons, to adjust available width with resizable leading/trailing bool scrolling_buttons = (tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); - float scrolling_buttons_width = GetTabBarScrollingButtonSize().x * 2.0f; + float scrolling_buttons_width = scrolling_buttons ? GetTabBarScrollingButtonSize().x * 2.0f : 0.0f; // Compute width - bool central_section_is_visible = tab_bar->Sections[0].WidthWithSpacing() + tab_bar->Sections[2].WidthWithSpacing() < tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f); + // FIXME: This is a mess, couldn't TabBarScrollingButtons() just be called earlier? + bool central_section_is_visible = (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) < (tab_bar->BarRect.GetWidth() - scrolling_buttons_width); float width_excess = central_section_is_visible - ? ImMax(tab_bar->Sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].WidthWithSpacing() - tab_bar->Sections[2].WidthWithSpacing()), 0.0f) - : (tab_bar->Sections[0].WidthWithSpacing() + tab_bar->Sections[2].WidthWithSpacing()) - (tab_bar->BarRect.GetWidth() - (scrolling_buttons ? scrolling_buttons_width : 0.0f)); + ? ImMax(sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - sections[0].WidthWithSpacing() - sections[2].WidthWithSpacing()), 0.0f) + : (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) - (tab_bar->BarRect.GetWidth() - scrolling_buttons_width); if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown || !central_section_is_visible)) { // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data + // FIXME: Why copying data, shouldn't we be able to call ShrinkWidths with the right offset and then use that in the loop below? if (central_section_is_visible) - memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[1].TabCount); // Move central section tabs + memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * sections[1].TabCount); // Move central section tabs else - memmove(g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount, g.ShrinkWidthBuffer.Data + tab_bar->Sections[0].TabCount + tab_bar->Sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * tab_bar->Sections[2].TabCount); // Replace central section tabs with trailing - int tab_n_shrinkable = (central_section_is_visible ? tab_bar->Sections[1].TabCount : tab_bar->Sections[0].TabCount + tab_bar->Sections[2].TabCount); + memmove(g.ShrinkWidthBuffer.Data + sections[0].TabCount, g.ShrinkWidthBuffer.Data + sections[0].TabCount + sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * sections[2].TabCount); // Replace central section tabs with trailing + int tab_n_shrinkable = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); - // Update each section width with shrink values + // Update each section width with shrunk values for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) { float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; - int section_n = tab->Flags & ImGuiTabItemFlags_Leading ? 0 : tab->Flags & ImGuiTabItemFlags_Trailing ? 2 : 1; + int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; - tab_bar->Sections[section_n].Width -= (tab->Width - shrinked_width); + sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; } } @@ -7126,8 +7122,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->WidthAllTabs = 0.0f; for (int section_n = 0; section_n < 3; section_n++) { - // TabBarScrollingButtons will alter BarRect.Max.x, so we need to anticipate BarRect width reduction - ImGuiTabBarSection* section = &tab_bar->Sections[section_n]; + // TabBarScrollingButtons() will alter BarRect.Max.x, so we need to anticipate BarRect width reduction + // FIXME: The +1.0f is in TabBarScrollingButtons() + ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - section->Width - (scrolling_buttons ? scrolling_buttons_width + 1.0f : 0.0f), next_tab_offset); @@ -7138,17 +7135,16 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) next_tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } tab_bar->WidthAllTabs += ImMax(section->WidthWithSpacing(), 0.0f); - next_tab_offset += section->InnerSpacing; + next_tab_offset += section->Spacing; } // Horizontal scrolling buttons if (scrolling_buttons) if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x { - if (scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button) - scroll_track_selected_tab_id = scroll_track_selected_tab->ID; - else - scroll_track_selected_tab_id = tab_bar->SelectedTabId = scroll_track_selected_tab->ID; + scroll_track_selected_tab_id = scroll_track_selected_tab->ID; + if (!(scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button)) + tab_bar->SelectedTabId = scroll_track_selected_tab_id; } // If we have lost the selected tab, select the next most recently active one @@ -7267,15 +7263,17 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) return; ImGuiContext& g = *GImGui; + ImGuiTabBarSection* sections = tab_bar->Sections; float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) int order = tab_bar->GetTabOrder(tab); // Scrolling happens only in the central section (leading/trailing sections are not scrolling) - float scrollable_width = tab_bar->BarRect.GetWidth() - tab_bar->Sections[0].Width - tab_bar->Sections[2].Width - tab_bar->Sections[1].InnerSpacing; + // FIXME: This is all confusing. + float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing; // We make all tabs positions all relative Sections[0].Width to make code simpler - float tab_x1 = tab->Offset - tab_bar->Sections[0].Width + (order > tab_bar->Sections[0].TabCount - 1 ? -margin : 0.0f); - float tab_x2 = tab->Offset - tab_bar->Sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - tab_bar->Sections[2].TabCount ? margin : 1.0f); + float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f); + float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f); tab_bar->ScrollingTargetDistToVisibility = 0.0f; if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) { @@ -7543,7 +7541,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab = &tab_bar->Tabs.back(); tab->ID = id; tab->Width = size.x; - tab_bar->TabsAddedNew = tab_is_new = true; + tab_bar->TabsAddedNew = true; + tab_is_new = true; } tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab); tab->ContentWidth = size.x; @@ -7609,8 +7608,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) // Leading buttons will be clipped by BarRect.Max.x, Trailing buttons will be clipped at BarRect.Min.x + LeadingsWidth (+ spacing if there are some buttons), and central tabs will be clipped inbetween - float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->Sections[2].Width + tab_bar->Sections[1].InnerSpacing; - float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->Sections[0].Width + tab_bar->Sections[0].InnerSpacing; + float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->Sections[2].Width + tab_bar->Sections[1].Spacing; + float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->Sections[0].Width + tab_bar->Sections[0].Spacing; bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x > tab_bar->BarRect.Max.x - offset_trailing); if (want_clip_rect) PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x + offset_leading), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x - offset_trailing, bb.Max.y), true); From 99f69eb1859bfdf84e4dd4338ab63362a48a51d9 Mon Sep 17 00:00:00 2001 From: Louis Schnellbach Date: Tue, 22 Sep 2020 10:58:10 +0200 Subject: [PATCH 26/28] Tab Bar: Moved up TabBarScrollingButtons function call. (#3291) --- imgui_widgets.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e224859a..d60223a2 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7082,16 +7082,22 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) for (int section_n = 0; section_n < 3; section_n++) tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; - // We want to know here if we'll need the scrolling buttons, to adjust available width with resizable leading/trailing - bool scrolling_buttons = (tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); - float scrolling_buttons_width = scrolling_buttons ? GetTabBarScrollingButtonSize().x * 2.0f : 0.0f; + // Horizontal scrolling buttons + // (Note that TabBarScrollButtons() will alter BarRect.Max.x) + if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) + if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) + { + scroll_track_selected_tab_id = scroll_track_selected_tab->ID; + if (!(scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button)) + tab_bar->SelectedTabId = scroll_track_selected_tab_id; + } // Compute width - // FIXME: This is a mess, couldn't TabBarScrollingButtons() just be called earlier? - bool central_section_is_visible = (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) < (tab_bar->BarRect.GetWidth() - scrolling_buttons_width); + // FIXME: This is a mess + bool central_section_is_visible = (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) < tab_bar->BarRect.GetWidth(); float width_excess = central_section_is_visible ? ImMax(sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - sections[0].WidthWithSpacing() - sections[2].WidthWithSpacing()), 0.0f) - : (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) - (tab_bar->BarRect.GetWidth() - scrolling_buttons_width); + : (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) - tab_bar->BarRect.GetWidth(); if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown || !central_section_is_visible)) { @@ -7126,7 +7132,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // FIXME: The +1.0f is in TabBarScrollingButtons() ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) - next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - section->Width - (scrolling_buttons ? scrolling_buttons_width + 1.0f : 0.0f), next_tab_offset); + next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - section->Width, next_tab_offset); for (int tab_n = 0; tab_n < section->TabCount; tab_n++) { @@ -7138,15 +7144,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) next_tab_offset += section->Spacing; } - // Horizontal scrolling buttons - if (scrolling_buttons) - if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x - { - scroll_track_selected_tab_id = scroll_track_selected_tab->ID; - if (!(scroll_track_selected_tab->Flags & ImGuiTabItemFlags_Button)) - tab_bar->SelectedTabId = scroll_track_selected_tab_id; - } - // If we have lost the selected tab, select the next most recently active one if (found_selected_tab_id == false) tab_bar->SelectedTabId = 0; From 6b76781c66403457e870e8de3943063acb2546ee Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2020 11:18:22 +0200 Subject: [PATCH 27/28] Tab Bar: Tidying up. Rework ShrinkWidths to allow marking tabs as not shrinkable (unused yet) + don't unnecessarily move data within ShrinkWidthBuffer. (#3291) --- imgui_internal.h | 3 +-- imgui_widgets.cpp | 68 +++++++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index b48e2cc4..7aa47618 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1729,11 +1729,10 @@ struct ImGuiTabBarSection { int TabStartIndex; // Index of first tab in this section. int TabCount; // Number of tabs in this section. - float Width; // Width of this section (after shrinking down) + float Width; // Sum of width of tabs in this section (after shrinking down) float Spacing; // Horizontal spacing at the end of the section. ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); } - float WidthWithSpacing() const { return Width + Spacing; } }; // Storage for a tab bar (sizeof() 92~96 bytes) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d60223a2..c551c314 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1426,11 +1426,13 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) } // Shrink excess width from a set of item, by removing width from the larger items first. +// Set items Width to -1.0f to disable shrinking this item. void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) { if (count == 1) { - items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + if (items[0].Width >= 0.0f) + items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); return; } ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); @@ -1439,7 +1441,9 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc { while (count_same_width < count && items[0].Width <= items[count_same_width].Width) count_same_width++; - float max_width_to_remove_per_item = (count_same_width < count) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + if (max_width_to_remove_per_item <= 0.0f) + break; float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); for (int item_n = 0; item_n < count_same_width; item_n++) items[item_n].Width -= width_to_remove_per_item; @@ -7043,10 +7047,14 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; - // Compute ideal widths + // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central + // (whereas our tabs are stored as: leading, central, trailing) + int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + + // Compute ideal tabs widths + store them into shrink buffer ImGuiTabItem* most_recently_selected_tab = NULL; bool found_selected_tab_id = false; - g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; @@ -7071,19 +7079,21 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) section->Width += tab->ContentWidth + (tab_n > section->TabStartIndex ? g.Style.ItemInnerSpacing.x : 0.0f); // Store data so we can build an array sorted by width if we need to shrink tabs down - g.ShrinkWidthBuffer[tab_n].Index = tab_n; - g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth; + int shrink_buffer_index = shrink_buffer_indexes[section_n]++; + g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n; + g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth; IM_ASSERT(tab->ContentWidth > 0.0f); tab->Width = tab->ContentWidth; } + // Compute total ideal width (used for e.g. auto-resizing a window) tab_bar->WidthAllTabsIdeal = 0.0f; for (int section_n = 0; section_n < 3; section_n++) tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; // Horizontal scrolling buttons - // (Note that TabBarScrollButtons() will alter BarRect.Max.x) + // (note that TabBarScrollButtons() will alter BarRect.Max.x) if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) if (ImGuiTabItem* scroll_track_selected_tab = TabBarScrollingButtons(tab_bar)) { @@ -7092,32 +7102,33 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->SelectedTabId = scroll_track_selected_tab_id; } - // Compute width - // FIXME: This is a mess - bool central_section_is_visible = (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) < tab_bar->BarRect.GetWidth(); - float width_excess = central_section_is_visible - ? ImMax(sections[1].WidthWithSpacing() - (tab_bar->BarRect.GetWidth() - sections[0].WidthWithSpacing() - sections[2].WidthWithSpacing()), 0.0f) - : (sections[0].WidthWithSpacing() + sections[2].WidthWithSpacing()) - tab_bar->BarRect.GetWidth(); + // Shrink widths if full tabs don't fit in their allocated space + float section_0_w = sections[0].Width + sections[0].Spacing; + float section_1_w = sections[1].Width + sections[1].Spacing; + float section_2_w = sections[2].Width + sections[2].Spacing; + bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth(); + float width_excess; + if (central_section_is_visible) + width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section + else + width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section - if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown || !central_section_is_visible)) + // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore + if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) { - // All tabs are in the ShrinkWidthBuffer, but we will only resize leading/trailing or central tabs, so rearrange internal data - // FIXME: Why copying data, shouldn't we be able to call ShrinkWidths with the right offset and then use that in the loop below? - if (central_section_is_visible) - memmove(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Data + sections[0].TabCount, sizeof(ImGuiShrinkWidthItem) * sections[1].TabCount); // Move central section tabs - else - memmove(g.ShrinkWidthBuffer.Data + sections[0].TabCount, g.ShrinkWidthBuffer.Data + sections[0].TabCount + sections[1].TabCount, sizeof(ImGuiShrinkWidthItem) * sections[2].TabCount); // Replace central section tabs with trailing - int tab_n_shrinkable = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); + int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); + int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); + ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); - ShrinkWidths(g.ShrinkWidthBuffer.Data, tab_n_shrinkable, width_excess); - - // Update each section width with shrunk values - for (int tab_n = 0; tab_n < tab_n_shrinkable; tab_n++) + // Apply shrunk values into tabs and sections + for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) { - float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; - int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; + float shrinked_width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width); + if (shrinked_width < 0.0f) + continue; + int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; } @@ -7128,7 +7139,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->WidthAllTabs = 0.0f; for (int section_n = 0; section_n < 3; section_n++) { - // TabBarScrollingButtons() will alter BarRect.Max.x, so we need to anticipate BarRect width reduction // FIXME: The +1.0f is in TabBarScrollingButtons() ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) @@ -7140,7 +7150,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab->Offset = next_tab_offset; next_tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } - tab_bar->WidthAllTabs += ImMax(section->WidthWithSpacing(), 0.0f); + tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); next_tab_offset += section->Spacing; } From 1ec464eb9aa077b7eae81e50fce8844a85625cc7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 22 Sep 2020 16:14:04 +0200 Subject: [PATCH 28/28] Tab Bar: Further simplification of section/clip rect handling. (#3291) --- imgui.cpp | 6 ++-- imgui_internal.h | 13 ++------- imgui_widgets.cpp | 70 +++++++++++++++++++++++------------------------ 3 files changed, 38 insertions(+), 51 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 1d65396a..f66cd751 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10563,10 +10563,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImDrawList* draw_list = ImGui::GetForegroundDrawList(); draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); - if (tab_bar->Sections[0].Width > 0.0f) - draw_list->AddLine(ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Min.x + tab_bar->Sections[0].Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); - if (tab_bar->Sections[2].Width > 0.0f) - draw_list->AddLine(ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x - tab_bar->Sections[2].Width, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); + draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); } if (open) { diff --git a/imgui_internal.h b/imgui_internal.h index 7aa47618..856b2a63 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1725,16 +1725,6 @@ struct ImGuiTabItem ImGuiTabItem() { ID = 0; Flags = ImGuiTabItemFlags_None; LastFrameVisible = LastFrameSelected = -1; NameOffset = -1; Offset = Width = ContentWidth = 0.0f; BeginOrder = -1; IndexDuringLayout = -1; WantClose = false; } }; -struct ImGuiTabBarSection -{ - int TabStartIndex; // Index of first tab in this section. - int TabCount; // Number of tabs in this section. - float Width; // Sum of width of tabs in this section (after shrinking down) - float Spacing; // Horizontal spacing at the end of the section. - - ImGuiTabBarSection(){ memset(this, 0, sizeof(*this)); } -}; - // Storage for a tab bar (sizeof() 92~96 bytes) struct ImGuiTabBar { @@ -1753,6 +1743,8 @@ struct ImGuiTabBar float ScrollingTarget; float ScrollingTargetDistToVisibility; float ScrollingSpeed; + float ScrollingRectMinX; + float ScrollingRectMaxX; ImGuiTabBarFlags Flags; ImGuiID ReorderRequestTabId; ImS8 ReorderRequestDir; @@ -1762,7 +1754,6 @@ struct ImGuiTabBar bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame short LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() ImVec2 FramePadding; // style.FramePadding locked at the time of BeginTabBar() - ImGuiTabBarSection Sections[3]; // Layout sections: Leading, Central, Trailing ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer. ImGuiTabBar(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index c551c314..2fef410d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6801,13 +6801,22 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - TabBarTabListPopupButton() [Internal] //------------------------------------------------------------------------- +struct ImGuiTabBarSection +{ + int TabCount; // Number of tabs in this section. + float Width; // Sum of width of tabs in this section (after shrinking down) + float Spacing; // Horizontal spacing at the end of the section. + + ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } +}; + namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); static float TabBarCalcMaxTabWidth(); static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); - static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImGuiTabBarSection* sections); static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); } @@ -6820,6 +6829,7 @@ ImGuiTabBar::ImGuiTabBar() LastTabContentHeight = 0.0f; WidthAllTabs = WidthAllTabsIdeal = 0.0f; ScrollingAnim = ScrollingTarget = ScrollingTargetDistToVisibility = ScrollingSpeed = 0.0f; + ScrollingRectMinX = ScrollingRectMaxX = 0.0f; Flags = ImGuiTabBarFlags_None; ReorderRequestTabId = 0; ReorderRequestDir = 0; @@ -6860,12 +6870,6 @@ static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) return ImGuiPtrOrIndex(tab_bar); } -static ImVec2 GetTabBarScrollingButtonSize() -{ - ImGuiContext& g = *GImGui; - return ImVec2(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); -} - bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; @@ -6969,14 +6973,13 @@ void ImGui::EndTabBar() static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; - ImGuiTabBarSection* sections = tab_bar->Sections; tab_bar->WantLayout = false; // Garbage collect by compacting list // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) int tab_dst_n = 0; bool need_sort_by_section = false; - sections[0].TabCount = sections[1].TabCount = sections[2].TabCount = 0; + ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; @@ -7015,13 +7018,6 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Calculate spacing between sections sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; - sections[2].Spacing = 0.0f; - sections[0].TabStartIndex = 0; - sections[1].TabStartIndex = sections[0].TabStartIndex + sections[0].TabCount; - sections[2].TabStartIndex = sections[1].TabStartIndex + sections[1].TabCount; - sections[0].Width = 0.0f; - sections[1].Width = 0.0f; - sections[2].Width = 0.0f; // Setup next selected tab ImGuiID scroll_track_selected_tab_id = 0; @@ -7054,6 +7050,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Compute ideal tabs widths + store them into shrink buffer ImGuiTabItem* most_recently_selected_tab = NULL; + int curr_section_n = -1; bool found_selected_tab_id = false; for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { @@ -7076,7 +7073,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) int section_n = (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; ImGuiTabBarSection* section = §ions[section_n]; - section->Width += tab->ContentWidth + (tab_n > section->TabStartIndex ? g.Style.ItemInnerSpacing.x : 0.0f); + section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); + curr_section_n = section_n; // Store data so we can build an array sorted by width if we need to shrink tabs down int shrink_buffer_index = shrink_buffer_indexes[section_n]++; @@ -7135,23 +7133,24 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } // Layout all active tabs - float next_tab_offset = 0.0f; + int section_tab_index = 0; + float tab_offset = 0.0f; tab_bar->WidthAllTabs = 0.0f; for (int section_n = 0; section_n < 3; section_n++) { - // FIXME: The +1.0f is in TabBarScrollingButtons() ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) - next_tab_offset = ImMin(tab_bar->BarRect.GetWidth() - section->Width, next_tab_offset); + tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset); for (int tab_n = 0; tab_n < section->TabCount; tab_n++) { - ImGuiTabItem* tab = &tab_bar->Tabs[section->TabStartIndex + tab_n]; - tab->Offset = next_tab_offset; - next_tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; + tab->Offset = tab_offset; + tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); } tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); - next_tab_offset += section->Spacing; + tab_offset += section->Spacing; + section_tab_index += section->TabCount; } // If we have lost the selected tab, select the next most recently active one @@ -7167,7 +7166,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Update scrolling if (scroll_track_selected_tab_id) if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id)) - TabBarScrollToTab(tab_bar, scroll_track_selected_tab); + TabBarScrollToTab(tab_bar, scroll_track_selected_tab, sections); tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) @@ -7183,6 +7182,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { tab_bar->ScrollingSpeed = 0.0f; } + tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing; + tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing; // Clear name buffers if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) @@ -7264,13 +7265,12 @@ static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) return ImMax(scrolling, 0.0f); } -static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImGuiTabBarSection* sections) { if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) return; ImGuiContext& g = *GImGui; - ImGuiTabBarSection* sections = tab_bar->Sections; float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) int order = tab_bar->GetTabOrder(tab); @@ -7336,7 +7336,7 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const ImVec2 arrow_button_size = GetTabBarScrollingButtonSize(); + const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); const float scrolling_buttons_width = arrow_button_size.x * 2.0f; const ImVec2 backup_cursor_pos = window->DC.CursorPos; @@ -7605,21 +7605,19 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; // Layout + const bool is_central_section = (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) == 0; size.x = tab->Width; - if (tab->Flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) - window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); - else + if (is_central_section) window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + else + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + size); // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) - // Leading buttons will be clipped by BarRect.Max.x, Trailing buttons will be clipped at BarRect.Min.x + LeadingsWidth (+ spacing if there are some buttons), and central tabs will be clipped inbetween - float offset_trailing = (flags & (ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_Leading)) ? 0.0f : tab_bar->Sections[2].Width + tab_bar->Sections[1].Spacing; - float offset_leading = (flags & ImGuiTabItemFlags_Leading) ? 0.0f : tab_bar->Sections[0].Width + tab_bar->Sections[0].Spacing; - bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x + offset_leading) || (bb.Max.x > tab_bar->BarRect.Max.x - offset_trailing); + const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); if (want_clip_rect) - PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x + offset_leading), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x - offset_trailing, bb.Max.y), true); + PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y);