From 9c8671e7b09fe8b11b418841e9baf81c9e2d46de Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 7 Oct 2020 12:04:28 +0200 Subject: [PATCH 001/144] Tables: Add empty file, skeleton. --- docs/CHANGELOG.txt | 1 + .../example_allegro5/example_allegro5.vcxproj | 1 + .../example_allegro5.vcxproj.filters | 3 + .../project.pbxproj | 15 ++-- .../project.pbxproj | 4 + examples/example_emscripten_opengl3/Makefile | 2 +- examples/example_glfw_metal/Makefile | 2 +- examples/example_glfw_opengl2/Makefile | 2 +- .../example_glfw_opengl2.vcxproj | 1 + .../example_glfw_opengl2.vcxproj.filters | 3 + examples/example_glfw_opengl3/Makefile | 2 +- .../example_glfw_opengl3.vcxproj | 1 + .../example_glfw_opengl3.vcxproj.filters | 3 + examples/example_glfw_vulkan/CMakeLists.txt | 2 +- .../example_glfw_vulkan.vcxproj | 1 + .../example_glfw_vulkan.vcxproj.filters | 3 + examples/example_glut_opengl2/Makefile | 2 +- .../example_glut_opengl2.vcxproj | 1 + .../example_glut_opengl2.vcxproj.filters | 3 + .../example_marmalade/marmalade_example.mkb | 1 + examples/example_null/Makefile | 2 +- .../example_sdl_directx11.vcxproj | 1 + .../example_sdl_directx11.vcxproj.filters | 3 + examples/example_sdl_metal/Makefile | 2 +- examples/example_sdl_opengl2/Makefile | 2 +- .../example_sdl_opengl2.vcxproj | 1 + .../example_sdl_opengl2.vcxproj.filters | 3 + examples/example_sdl_opengl3/Makefile | 2 +- .../example_sdl_opengl3.vcxproj | 3 +- .../example_sdl_opengl3.vcxproj.filters | 3 + .../example_sdl_vulkan.vcxproj | 3 +- .../example_win32_directx10.vcxproj | 3 +- .../example_win32_directx10.vcxproj.filters | 5 +- .../example_win32_directx11.vcxproj | 1 + .../example_win32_directx11.vcxproj.filters | 3 + .../example_win32_directx12.vcxproj | 3 +- .../example_win32_directx12.vcxproj.filters | 5 +- .../example_win32_directx9.vcxproj | 3 +- .../example_win32_directx9.vcxproj.filters | 5 +- imgui.cpp | 1 + imgui_tables.cpp | 73 +++++++++++++++++++ misc/single_file/imgui_single_file.h | 1 + 42 files changed, 158 insertions(+), 23 deletions(-) create mode 100644 imgui_tables.cpp diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 7ca42c4b..6b92eecf 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -37,6 +37,7 @@ HOW TO UPDATE? Breaking Changes: +- Added imgui_tables.cpp file! Manually constructed project files will need the new file added! - Backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/. (#3513) - Removed redirecting functions/enums names that were marked obsolete in 1.60 (April 2017): - io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render function of your backend diff --git a/examples/example_allegro5/example_allegro5.vcxproj b/examples/example_allegro5/example_allegro5.vcxproj index d50318a9..c6c524aa 100644 --- a/examples/example_allegro5/example_allegro5.vcxproj +++ b/examples/example_allegro5/example_allegro5.vcxproj @@ -158,6 +158,7 @@ + diff --git a/examples/example_allegro5/example_allegro5.vcxproj.filters b/examples/example_allegro5/example_allegro5.vcxproj.filters index 9abb67e7..00873a22 100644 --- a/examples/example_allegro5/example_allegro5.vcxproj.filters +++ b/examples/example_allegro5/example_allegro5.vcxproj.filters @@ -28,6 +28,9 @@ sources + + imgui + imgui diff --git a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj index f382520f..040fcd64 100644 --- a/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj +++ b/examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; }; 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 07A82ED72139413C0078D120 /* imgui_widgets.cpp */; }; + 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822D257677DB0038A28D /* imgui_tables.cpp */; }; 8309BD8F253CCAAA0045E2A1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */; }; 8309BDA5253CCC070045E2A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDA0253CCBC10045E2A1 /* main.mm */; }; 8309BDA8253CCC080045E2A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8309BDA0253CCBC10045E2A1 /* main.mm */; }; @@ -33,6 +34,7 @@ /* Begin PBXFileReference section */ 07A82ED62139413C0078D120 /* imgui_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui_internal.h; path = ../../imgui_internal.h; sourceTree = ""; }; 07A82ED72139413C0078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = ""; }; + 5079822D257677DB0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = ""; }; 8307E7C420E9F9C900473790 /* example_apple_metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_apple_metal.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8307E7DA20E9F9C900473790 /* example_apple_metal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example_apple_metal.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8309BD8E253CCAAA0045E2A1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -146,6 +148,7 @@ 83BBE9F020EB544400295997 /* imgui */ = { isa = PBXGroup; children = ( + 5079822D257677DB0038A28D /* imgui_tables.cpp */, 8309BDB5253CCC9D0045E2A1 /* imgui_impl_metal.mm */, 8309BDB6253CCC9D0045E2A1 /* imgui_impl_osx.mm */, 83BBEA0420EB54E700295997 /* imconfig.h */, @@ -259,10 +262,11 @@ buildActionMask = 2147483647; files = ( 8309BDBB253CCCAD0045E2A1 /* imgui_impl_metal.mm in Sources */, - 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */, 83BBEA0920EB54E700295997 /* imgui.cpp in Sources */, 83BBEA0720EB54E700295997 /* imgui_demo.cpp in Sources */, - 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */, + 83BBEA0520EB54E700295997 /* imgui_draw.cpp in Sources */, + 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */, + 07A82ED82139413D0078D120 /* imgui_widgets.cpp in Sources */, 8309BDA5253CCC070045E2A1 /* main.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -273,10 +277,11 @@ files = ( 8309BDBE253CCCB60045E2A1 /* imgui_impl_metal.mm in Sources */, 8309BDBF253CCCB60045E2A1 /* imgui_impl_osx.mm in Sources */, - 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */, + 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */, + 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */, + 83BBEA0620EB54E700295997 /* imgui_draw.cpp in Sources */, + 5079822E257677DB0038A28D /* imgui_tables.cpp in Sources */, 07A82ED92139418F0078D120 /* imgui_widgets.cpp in Sources */, - 83BBEA0A20EB54E700295997 /* imgui.cpp in Sources */, - 83BBEA0820EB54E700295997 /* imgui_demo.cpp in Sources */, 8309BDA8253CCC080045E2A1 /* main.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj b/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj index 204b68a9..42d6095d 100644 --- a/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj +++ b/examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 4080A9B020B0347A0036BA46 /* imgui_impl_osx.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4080A99F20B034280036BA46 /* imgui_impl_osx.mm */; }; 4080A9B320B034E40036BA46 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4080A9B220B034E40036BA46 /* Cocoa.framework */; }; 4080A9B520B034EA0036BA46 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4080A9B420B034EA0036BA46 /* OpenGL.framework */; }; + 50798230257677FD0038A28D /* imgui_tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5079822F257677FC0038A28D /* imgui_tables.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -46,6 +47,7 @@ 4080A9AC20B0343C0036BA46 /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = ""; }; 4080A9B220B034E40036BA46 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 4080A9B420B034EA0036BA46 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 5079822F257677FC0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +66,7 @@ 4080A96220B029B00036BA46 = { isa = PBXGroup; children = ( + 5079822F257677FC0038A28D /* imgui_tables.cpp */, 4080A9AC20B0343C0036BA46 /* imconfig.h */, 4080A9A720B0343C0036BA46 /* imgui.cpp */, 4080A9A820B0343C0036BA46 /* imgui.h */, @@ -161,6 +164,7 @@ 4080A9A220B034280036BA46 /* imgui_impl_opengl2.cpp in Sources */, 4080A9B020B0347A0036BA46 /* imgui_impl_osx.mm in Sources */, 4080A9AE20B0343C0036BA46 /* imgui.cpp in Sources */, + 50798230257677FD0038A28D /* imgui_tables.cpp in Sources */, 07A82EDB213941D00078D120 /* imgui_widgets.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/example_emscripten_opengl3/Makefile b/examples/example_emscripten_opengl3/Makefile index 06baf499..cde9ffa3 100644 --- a/examples/example_emscripten_opengl3/Makefile +++ b/examples/example_emscripten_opengl3/Makefile @@ -18,7 +18,7 @@ CXX = em++ EXE = example_emscripten_opengl3.html IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_glfw_metal/Makefile b/examples/example_glfw_metal/Makefile index 4b7599eb..8f08b965 100644 --- a/examples/example_glfw_metal/Makefile +++ b/examples/example_glfw_metal/Makefile @@ -9,7 +9,7 @@ EXE = example_glfw_metal IMGUI_DIR = ../.. SOURCES = main.mm -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_metal.mm OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) diff --git a/examples/example_glfw_opengl2/Makefile b/examples/example_glfw_opengl2/Makefile index 6bcbca5e..a24a91f3 100644 --- a/examples/example_glfw_opengl2/Makefile +++ b/examples/example_glfw_opengl2/Makefile @@ -17,7 +17,7 @@ EXE = example_glfw_opengl2 IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl2.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj index 35ffdfd9..2322ce25 100644 --- a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj +++ b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj @@ -158,6 +158,7 @@ + diff --git a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters index 4b4dd526..8327557c 100644 --- a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters +++ b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters @@ -22,6 +22,9 @@ imgui + + imgui + imgui diff --git a/examples/example_glfw_opengl3/Makefile b/examples/example_glfw_opengl3/Makefile index 1524ac6f..294d103d 100644 --- a/examples/example_glfw_opengl3/Makefile +++ b/examples/example_glfw_opengl3/Makefile @@ -17,7 +17,7 @@ EXE = example_glfw_opengl3 IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj index 9c823935..61184d87 100644 --- a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj +++ b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj @@ -158,6 +158,7 @@ + diff --git a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters index 000e8d28..6e3859c1 100644 --- a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters +++ b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters @@ -28,6 +28,9 @@ imgui + + imgui + imgui diff --git a/examples/example_glfw_vulkan/CMakeLists.txt b/examples/example_glfw_vulkan/CMakeLists.txt index 801aee4d..05eab3bc 100644 --- a/examples/example_glfw_vulkan/CMakeLists.txt +++ b/examples/example_glfw_vulkan/CMakeLists.txt @@ -39,5 +39,5 @@ include_directories(${GLFW_DIR}/deps) file(GLOB sources *.cpp) -add_executable(example_glfw_vulkan ${sources} ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_demo.cpp ${IMGUI_DIR}/imgui_widgets.cpp) +add_executable(example_glfw_vulkan ${sources} ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_demo.cpp ${IMGUI_DIR}/imgui_tables.cpp ${IMGUI_DIR}/imgui_widgets.cpp) target_link_libraries(example_glfw_vulkan ${LIBRARIES}) diff --git a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj index d9418579..1667b5ab 100644 --- a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj +++ b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj @@ -158,6 +158,7 @@ + diff --git a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters index 43f5f5b6..943eb3dd 100644 --- a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters +++ b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters @@ -22,6 +22,9 @@ imgui + + imgui + imgui diff --git a/examples/example_glut_opengl2/Makefile b/examples/example_glut_opengl2/Makefile index 21984cec..952ca318 100644 --- a/examples/example_glut_opengl2/Makefile +++ b/examples/example_glut_opengl2/Makefile @@ -12,7 +12,7 @@ EXE = example_glut_opengl2 IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glut.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl2.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_glut_opengl2/example_glut_opengl2.vcxproj b/examples/example_glut_opengl2/example_glut_opengl2.vcxproj index 736e6e7a..f14ea156 100644 --- a/examples/example_glut_opengl2/example_glut_opengl2.vcxproj +++ b/examples/example_glut_opengl2/example_glut_opengl2.vcxproj @@ -158,6 +158,7 @@ + diff --git a/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters b/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters index 8f8fd955..69882910 100644 --- a/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters +++ b/examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters @@ -22,6 +22,9 @@ imgui + + imgui + imgui diff --git a/examples/example_marmalade/marmalade_example.mkb b/examples/example_marmalade/marmalade_example.mkb index bf5d1b86..4e765f16 100644 --- a/examples/example_marmalade/marmalade_example.mkb +++ b/examples/example_marmalade/marmalade_example.mkb @@ -33,6 +33,7 @@ files ../../imgui.cpp ../../imgui_demo.cpp ../../imgui_draw.cpp + ../../imgui_tables.cpp ../../imgui_widgets.cpp ../../imconfig.h ../../imgui.h diff --git a/examples/example_null/Makefile b/examples/example_null/Makefile index 25cecd82..edf0145e 100644 --- a/examples/example_null/Makefile +++ b/examples/example_null/Makefile @@ -13,7 +13,7 @@ WITH_FREETYPE ?= 0 EXE = example_null IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_sdl_directx11/example_sdl_directx11.vcxproj b/examples/example_sdl_directx11/example_sdl_directx11.vcxproj index 690c660d..99dd54af 100644 --- a/examples/example_sdl_directx11/example_sdl_directx11.vcxproj +++ b/examples/example_sdl_directx11/example_sdl_directx11.vcxproj @@ -159,6 +159,7 @@ + diff --git a/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters b/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters index d1f0876e..8ebfd6d1 100644 --- a/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters +++ b/examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters @@ -38,6 +38,9 @@ imgui + + imgui + imgui diff --git a/examples/example_sdl_metal/Makefile b/examples/example_sdl_metal/Makefile index 53c8aabb..042bb04c 100644 --- a/examples/example_sdl_metal/Makefile +++ b/examples/example_sdl_metal/Makefile @@ -9,7 +9,7 @@ EXE = example_sdl_metal IMGUI_DIR = ../.. SOURCES = main.mm -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl.cpp $(IMGUI_DIR)/backends/imgui_impl_metal.mm OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) diff --git a/examples/example_sdl_opengl2/Makefile b/examples/example_sdl_opengl2/Makefile index 2da745d7..5fdad0ba 100644 --- a/examples/example_sdl_opengl2/Makefile +++ b/examples/example_sdl_opengl2/Makefile @@ -17,7 +17,7 @@ EXE = example_sdl_opengl2 IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl2.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj b/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj index b8eb9219..6b9e642d 100644 --- a/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj +++ b/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj @@ -158,6 +158,7 @@ + diff --git a/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters b/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters index 65acfe43..643b0ed7 100644 --- a/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters +++ b/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters @@ -22,6 +22,9 @@ sources + + imgui + imgui diff --git a/examples/example_sdl_opengl3/Makefile b/examples/example_sdl_opengl3/Makefile index b408ce35..6e30a135 100644 --- a/examples/example_sdl_opengl3/Makefile +++ b/examples/example_sdl_opengl3/Makefile @@ -17,7 +17,7 @@ EXE = example_sdl_opengl3 IMGUI_DIR = ../.. SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SOURCES += $(IMGUI_DIR)/backends/imgui_impl_sdl.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) UNAME_S := $(shell uname -s) diff --git a/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj b/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj index c8d67f98..7ef1a792 100644 --- a/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj +++ b/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj @@ -158,6 +158,7 @@ + @@ -180,4 +181,4 @@ - \ No newline at end of file + diff --git a/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters b/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters index ade2c96c..f6e323de 100644 --- a/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters +++ b/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters @@ -34,6 +34,9 @@ sources + + imgui + imgui diff --git a/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj b/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj index 84cc94b4..8567b790 100644 --- a/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj +++ b/examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj @@ -158,6 +158,7 @@ + @@ -177,4 +178,4 @@ - \ No newline at end of file + diff --git a/examples/example_win32_directx10/example_win32_directx10.vcxproj b/examples/example_win32_directx10/example_win32_directx10.vcxproj index 4a24dc1b..16e24a37 100644 --- a/examples/example_win32_directx10/example_win32_directx10.vcxproj +++ b/examples/example_win32_directx10/example_win32_directx10.vcxproj @@ -155,6 +155,7 @@ + @@ -167,4 +168,4 @@ - \ No newline at end of file + diff --git a/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters b/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters index 16107c9c..f76be9d0 100644 --- a/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters +++ b/examples/example_win32_directx10/example_win32_directx10.vcxproj.filters @@ -44,6 +44,9 @@ sources + + imgui + imgui @@ -54,4 +57,4 @@ sources - \ No newline at end of file + diff --git a/examples/example_win32_directx11/example_win32_directx11.vcxproj b/examples/example_win32_directx11/example_win32_directx11.vcxproj index e9945db9..4982050f 100644 --- a/examples/example_win32_directx11/example_win32_directx11.vcxproj +++ b/examples/example_win32_directx11/example_win32_directx11.vcxproj @@ -154,6 +154,7 @@ + diff --git a/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters b/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters index 6956b586..56defdde 100644 --- a/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters +++ b/examples/example_win32_directx11/example_win32_directx11.vcxproj.filters @@ -47,6 +47,9 @@ sources + + imgui + diff --git a/examples/example_win32_directx12/example_win32_directx12.vcxproj b/examples/example_win32_directx12/example_win32_directx12.vcxproj index 452328ce..ab8ee013 100644 --- a/examples/example_win32_directx12/example_win32_directx12.vcxproj +++ b/examples/example_win32_directx12/example_win32_directx12.vcxproj @@ -157,6 +157,7 @@ + @@ -168,4 +169,4 @@ - \ No newline at end of file + diff --git a/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters b/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters index 754c2954..91fc7343 100644 --- a/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters +++ b/examples/example_win32_directx12/example_win32_directx12.vcxproj.filters @@ -44,6 +44,9 @@ sources + + imgui + imgui @@ -51,4 +54,4 @@ - \ No newline at end of file + diff --git a/examples/example_win32_directx9/example_win32_directx9.vcxproj b/examples/example_win32_directx9/example_win32_directx9.vcxproj index a33833b3..747dcebe 100644 --- a/examples/example_win32_directx9/example_win32_directx9.vcxproj +++ b/examples/example_win32_directx9/example_win32_directx9.vcxproj @@ -148,6 +148,7 @@ + @@ -167,4 +168,4 @@ - \ No newline at end of file + diff --git a/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters b/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters index 2a684553..5197644e 100644 --- a/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters +++ b/examples/example_win32_directx9/example_win32_directx9.vcxproj.filters @@ -28,6 +28,9 @@ sources + + imgui + imgui @@ -55,4 +58,4 @@ sources - \ No newline at end of file + diff --git a/imgui.cpp b/imgui.cpp index 6fdb3b95..ad219eb9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -371,6 +371,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/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added! - 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of incoming Tables API. - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will apply to other data structures - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/. diff --git a/imgui_tables.cpp b/imgui_tables.cpp new file mode 100644 index 00000000..2dbe9984 --- /dev/null +++ b/imgui_tables.cpp @@ -0,0 +1,73 @@ +// dear imgui, v1.80 WIP +// (tables and columns code) + +/* + * + * Index of this file: + * + * // [SECTION] Widgets: BeginTable, EndTable, etc. + * // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. + * + */ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types +#endif +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#endif + + +//----------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTable, EndTable, etc. +//----------------------------------------------------------------------------- + + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. +// (This is a legacy API, prefer using BeginTable/EndTable!) +//------------------------------------------------------------------------- + + +//------------------------------------------------------------------------- + +#endif // #ifndef IMGUI_DISABLE diff --git a/misc/single_file/imgui_single_file.h b/misc/single_file/imgui_single_file.h index 6c849441..6c1fb369 100644 --- a/misc/single_file/imgui_single_file.h +++ b/misc/single_file/imgui_single_file.h @@ -13,5 +13,6 @@ #include "../../imgui.cpp" #include "../../imgui_demo.cpp" #include "../../imgui_draw.cpp" +#include "../../imgui_tables.cpp" #include "../../imgui_widgets.cpp" #endif From 818e1a4eb4cfa08b0151b92217c8fcc6d70595ff Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 30 Nov 2020 17:59:39 +0100 Subject: [PATCH 002/144] Tables: Moving legacy Columns code --- imgui_tables.cpp | 434 +++++++++++++++++++++++++++++++++++++++++++++ imgui_widgets.cpp | 441 ---------------------------------------------- 2 files changed, 434 insertions(+), 441 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2dbe9984..67b625da 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -66,6 +66,440 @@ // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. // (This is a legacy API, prefer using BeginTable/EndTable!) //------------------------------------------------------------------------- +// - SetWindowClipRectBeforeSetChannel() [Internal] +// - GetColumnIndex() +// - GetColumnsCount() +// - GetColumnOffset() +// - GetColumnWidth() +// - SetColumnOffset() +// - SetColumnWidth() +// - PushColumnClipRect() [Internal] +// - PushColumnsBackground() [Internal] +// - PopColumnsBackground() [Internal] +// - FindOrCreateColumns() [Internal] +// - GetColumnsID() [Internal] +// - BeginColumns() +// - NextColumn() +// - EndColumns() +// - Columns() +//------------------------------------------------------------------------- + +// [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences, +// they would meddle many times with the underlying ImDrawCmd. +// Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let +// the subsequent single call to SetCurrentChannel() does it things once. +void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect) +{ + ImVec4 clip_rect_vec4 = clip_rect.ToVec4(); + window->ClipRect = clip_rect; + window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4; + window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4; +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1; +} + +float ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm) +{ + return offset_norm * (columns->OffMaxX - columns->OffMinX); +} + +float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset) +{ + return offset / (columns->OffMaxX - columns->OffMinX); +} + +static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f; + +static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns == NULL) + return 0.0f; + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return ImGui::GetColumnOffsetFromNorm(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns == NULL) + return GetContentRegionAvail().x; + + if (column_index < 0) + column_index = columns->Current; + return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiOldColumns* columns = window->DC.CurrentColumns; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (column_index < 0) + column_index = columns->Current; + + ImGuiOldColumnData* column = &columns->Columns[column_index]; + PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); +} + +// Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns) +void ImGui::PushColumnsBackground() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns->Count == 1) + return; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + columns->HostBackupClipRect = window->ClipRect; + SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect); + columns->Splitter.SetCurrentChannel(window->DrawList, 0); +} + +void ImGui::PopColumnsBackground() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns->Count == 1) + return; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect); + columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); +} + +ImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id) +{ + // We have few columns per window so for now we don't need bother much with turning this into a faster lookup. + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiOldColumns()); + ImGuiOldColumns* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count) +{ + ImGuiWindow* window = GetCurrentWindow(); + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + return id; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count >= 1); + IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported + + // Acquire storage for the columns set + ImGuiID id = GetColumnsID(str_id, columns_count); + ImGuiOldColumns* columns = FindOrCreateColumns(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.CurrentColumns = columns; + + columns->HostCursorPosY = window->DC.CursorPos.y; + columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x; + columns->HostInitialClipRect = window->ClipRect; + columns->HostBackupParentWorkRect = window->ParentWorkRect; + window->ParentWorkRect = window->WorkRect; + + // Set state for first column + // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect + const float column_padding = g.Style.ItemSpacing.x; + const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize)); + const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f); + const float max_2 = window->WorkRect.Max.x + half_clip_extend_x; + columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f); + columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f); + columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize default widths + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiOldColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count; n++) + { + // Compute clipping rectangle + ImGuiOldColumnData* column = &columns->Columns[n]; + 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.ClipWithFull(window->ClipRect); + } + + if (columns->Count > 1) + { + columns->Splitter.Split(window->DrawList, 1 + columns->Count); + columns->Splitter.SetCurrentChannel(window->DrawList, 1); + PushColumnClipRect(0); + } + + // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user. + float offset_0 = GetColumnOffset(columns->Current); + float offset_1 = GetColumnOffset(columns->Current + 1); + float width = offset_1 - offset_0; + PushItemWidth(width * 0.65f); + window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; +} + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.CurrentColumns == NULL) + return; + + ImGuiContext& g = *GImGui; + ImGuiOldColumns* columns = window->DC.CurrentColumns; + + if (columns->Count == 1) + { + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + IM_ASSERT(columns->Current == 0); + return; + } + + // Next column + if (++columns->Current == columns->Count) + columns->Current = 0; + + PopItemWidth(); + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect() + // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them), + ImGuiOldColumnData* column = &columns->Columns[columns->Current]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); + + const float column_padding = g.Style.ItemSpacing.x; + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + if (columns->Current > 0) + { + // Columns 1+ ignore IndentX (by canceling it out) + // FIXME-COLUMNS: Unnecessary, could be locked? + window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding; + } + else + { + // New row/line: column 0 honor IndentX. + window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); + columns->LineMinY = columns->LineMaxY; + } + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + window->DC.CursorPos.y = columns->LineMinY; + window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + window->DC.CurrLineTextBaseOffset = 0.0f; + + // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup. + float offset_0 = GetColumnOffset(columns->Current); + float offset_1 = GetColumnOffset(columns->Current + 1); + float width = offset_1 - offset_0; + PushItemWidth(width * 0.65f); + window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiOldColumns* columns = window->DC.CurrentColumns; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + if (columns->Count > 1) + { + PopClipRect(); + columns->Splitter.Merge(window->DrawList); + } + + const ImGuiOldColumnFlags flags = columns->Flags; + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->LineMaxY; + if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy + bool is_being_resized = false; + if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems) + { + // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers. + const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y); + const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y); + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + ImGuiOldColumnData* column = &columns->Columns[n]; + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH; + const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_hit_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(flags & ImGuiOldColumnFlags_NoResize)) + { + ButtonBehavior(column_hit_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize)) + dragging_column = n; + } + + // Draw column + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = IM_FLOOR(x); + window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->WorkRect = window->ParentWorkRect; + window->ParentWorkRect = columns->HostBackupParentWorkRect; + window->DC.CurrentColumns = NULL; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); +} + +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + + ImGuiOldColumnFlags flags = (border ? 0 : ImGuiOldColumnFlags_NoBorder); + //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior + ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns != NULL && columns->Count == columns_count && columns->Flags == flags) + return; + + if (columns != NULL) + EndColumns(); + + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} //------------------------------------------------------------------------- diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2ecb5656..3a30847d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7866,445 +7866,4 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, } -//------------------------------------------------------------------------- -// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. -// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system. -//------------------------------------------------------------------------- -// - SetWindowClipRectBeforeSetChannel() [Internal] -// - GetColumnIndex() -// - GetColumnCount() -// - GetColumnOffset() -// - GetColumnWidth() -// - SetColumnOffset() -// - SetColumnWidth() -// - PushColumnClipRect() [Internal] -// - PushColumnsBackground() [Internal] -// - PopColumnsBackground() [Internal] -// - FindOrCreateColumns() [Internal] -// - GetColumnsID() [Internal] -// - BeginColumns() -// - NextColumn() -// - EndColumns() -// - Columns() -//------------------------------------------------------------------------- - -// [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences, -// they would meddle many times with the underlying ImDrawCmd. -// Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let -// the subsequent single call to SetCurrentChannel() does it things once. -void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect) -{ - ImVec4 clip_rect_vec4 = clip_rect.ToVec4(); - window->ClipRect = clip_rect; - window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4; - window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4; -} - -int ImGui::GetColumnIndex() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0; -} - -int ImGui::GetColumnsCount() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1; -} - -float ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm) -{ - return offset_norm * (columns->OffMaxX - columns->OffMinX); -} - -float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset) -{ - return offset / (columns->OffMaxX - columns->OffMinX); -} - -static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f; - -static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) -{ - // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing - // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. - IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); - - float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x; - x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); - if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths)) - x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); - - return x; -} - -float ImGui::GetColumnOffset(int column_index) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiOldColumns* columns = window->DC.CurrentColumns; - if (columns == NULL) - return 0.0f; - - if (column_index < 0) - column_index = columns->Current; - IM_ASSERT(column_index < columns->Columns.Size); - - const float t = columns->Columns[column_index].OffsetNorm; - const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t); - return x_offset; -} - -static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false) -{ - if (column_index < 0) - column_index = columns->Current; - - float offset_norm; - if (before_resize) - offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; - else - offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; - return ImGui::GetColumnOffsetFromNorm(columns, offset_norm); -} - -float ImGui::GetColumnWidth(int column_index) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiOldColumns* columns = window->DC.CurrentColumns; - if (columns == NULL) - return GetContentRegionAvail().x; - - if (column_index < 0) - column_index = columns->Current; - return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); -} - -void ImGui::SetColumnOffset(int column_index, float offset) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiOldColumns* columns = window->DC.CurrentColumns; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - IM_ASSERT(column_index < columns->Columns.Size); - - const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1); - const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; - - if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow)) - offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); - columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX); - - if (preserve_width) - SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); -} - -void ImGui::SetColumnWidth(int column_index, float width) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiOldColumns* columns = window->DC.CurrentColumns; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); -} - -void ImGui::PushColumnClipRect(int column_index) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiOldColumns* columns = window->DC.CurrentColumns; - if (column_index < 0) - column_index = columns->Current; - - ImGuiOldColumnData* column = &columns->Columns[column_index]; - PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); -} - -// Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns) -void ImGui::PushColumnsBackground() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiOldColumns* columns = window->DC.CurrentColumns; - if (columns->Count == 1) - return; - - // Optimization: avoid SetCurrentChannel() + PushClipRect() - columns->HostBackupClipRect = window->ClipRect; - SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect); - columns->Splitter.SetCurrentChannel(window->DrawList, 0); -} - -void ImGui::PopColumnsBackground() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiOldColumns* columns = window->DC.CurrentColumns; - if (columns->Count == 1) - return; - - // Optimization: avoid PopClipRect() + SetCurrentChannel() - SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect); - columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); -} - -ImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id) -{ - // We have few columns per window so for now we don't need bother much with turning this into a faster lookup. - for (int n = 0; n < window->ColumnsStorage.Size; n++) - if (window->ColumnsStorage[n].ID == id) - return &window->ColumnsStorage[n]; - - window->ColumnsStorage.push_back(ImGuiOldColumns()); - ImGuiOldColumns* columns = &window->ColumnsStorage.back(); - columns->ID = id; - return columns; -} - -ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count) -{ - ImGuiWindow* window = GetCurrentWindow(); - - // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. - // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. - PushID(0x11223347 + (str_id ? 0 : columns_count)); - ImGuiID id = window->GetID(str_id ? str_id : "columns"); - PopID(); - - return id; -} - -void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - - IM_ASSERT(columns_count >= 1); - IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported - - // Acquire storage for the columns set - ImGuiID id = GetColumnsID(str_id, columns_count); - ImGuiOldColumns* columns = FindOrCreateColumns(window, id); - IM_ASSERT(columns->ID == id); - columns->Current = 0; - columns->Count = columns_count; - columns->Flags = flags; - window->DC.CurrentColumns = columns; - - columns->HostCursorPosY = window->DC.CursorPos.y; - columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x; - columns->HostInitialClipRect = window->ClipRect; - columns->HostBackupParentWorkRect = window->ParentWorkRect; - window->ParentWorkRect = window->WorkRect; - - // Set state for first column - // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect - const float column_padding = g.Style.ItemSpacing.x; - const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize)); - const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f); - const float max_2 = window->WorkRect.Max.x + half_clip_extend_x; - columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f); - columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f); - columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; - - // Clear data if columns count changed - if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) - columns->Columns.resize(0); - - // Initialize default widths - columns->IsFirstFrame = (columns->Columns.Size == 0); - if (columns->Columns.Size == 0) - { - columns->Columns.reserve(columns_count + 1); - for (int n = 0; n < columns_count + 1; n++) - { - ImGuiOldColumnData column; - column.OffsetNorm = n / (float)columns_count; - columns->Columns.push_back(column); - } - } - - for (int n = 0; n < columns_count; n++) - { - // Compute clipping rectangle - ImGuiOldColumnData* column = &columns->Columns[n]; - 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.ClipWithFull(window->ClipRect); - } - - if (columns->Count > 1) - { - columns->Splitter.Split(window->DrawList, 1 + columns->Count); - columns->Splitter.SetCurrentChannel(window->DrawList, 1); - PushColumnClipRect(0); - } - - // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user. - float offset_0 = GetColumnOffset(columns->Current); - float offset_1 = GetColumnOffset(columns->Current + 1); - float width = offset_1 - offset_0; - PushItemWidth(width * 0.65f); - window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); - window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); - window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; -} - -void ImGui::NextColumn() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems || window->DC.CurrentColumns == NULL) - return; - - ImGuiContext& g = *GImGui; - ImGuiOldColumns* columns = window->DC.CurrentColumns; - - if (columns->Count == 1) - { - window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); - IM_ASSERT(columns->Current == 0); - return; - } - - // Next column - if (++columns->Current == columns->Count) - columns->Current = 0; - - PopItemWidth(); - - // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect() - // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them), - ImGuiOldColumnData* column = &columns->Columns[columns->Current]; - SetWindowClipRectBeforeSetChannel(window, column->ClipRect); - columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); - - const float column_padding = g.Style.ItemSpacing.x; - columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); - if (columns->Current > 0) - { - // Columns 1+ ignore IndentX (by canceling it out) - // FIXME-COLUMNS: Unnecessary, could be locked? - window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding; - } - else - { - // New row/line: column 0 honor IndentX. - window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); - columns->LineMinY = columns->LineMaxY; - } - window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); - window->DC.CursorPos.y = columns->LineMinY; - window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); - window->DC.CurrLineTextBaseOffset = 0.0f; - - // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup. - float offset_0 = GetColumnOffset(columns->Current); - float offset_1 = GetColumnOffset(columns->Current + 1); - float width = offset_1 - offset_0; - PushItemWidth(width * 0.65f); - window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; -} - -void ImGui::EndColumns() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiOldColumns* columns = window->DC.CurrentColumns; - IM_ASSERT(columns != NULL); - - PopItemWidth(); - if (columns->Count > 1) - { - PopClipRect(); - columns->Splitter.Merge(window->DrawList); - } - - const ImGuiOldColumnFlags flags = columns->Flags; - columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); - window->DC.CursorPos.y = columns->LineMaxY; - if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize)) - window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent - - // Draw columns borders and handle resize - // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy - bool is_being_resized = false; - if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems) - { - // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers. - const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y); - const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y); - int dragging_column = -1; - for (int n = 1; n < columns->Count; n++) - { - ImGuiOldColumnData* column = &columns->Columns[n]; - float x = window->Pos.x + GetColumnOffset(n); - const ImGuiID column_id = columns->ID + ImGuiID(n); - const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH; - const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); - KeepAliveID(column_id); - if (IsClippedEx(column_hit_rect, column_id, false)) - continue; - - bool hovered = false, held = false; - if (!(flags & ImGuiOldColumnFlags_NoResize)) - { - ButtonBehavior(column_hit_rect, column_id, &hovered, &held); - if (hovered || held) - g.MouseCursor = ImGuiMouseCursor_ResizeEW; - if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize)) - dragging_column = n; - } - - // Draw column - const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); - const float xi = IM_FLOOR(x); - window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); - } - - // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. - if (dragging_column != -1) - { - if (!columns->IsBeingResized) - for (int n = 0; n < columns->Count + 1; n++) - columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; - columns->IsBeingResized = is_being_resized = true; - float x = GetDraggedColumnOffset(columns, dragging_column); - SetColumnOffset(dragging_column, x); - } - } - columns->IsBeingResized = is_being_resized; - - window->WorkRect = window->ParentWorkRect; - window->ParentWorkRect = columns->HostBackupParentWorkRect; - window->DC.CurrentColumns = NULL; - window->DC.ColumnsOffset.x = 0.0f; - window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); -} - -void ImGui::Columns(int columns_count, const char* id, bool border) -{ - ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(columns_count >= 1); - - ImGuiOldColumnFlags flags = (border ? 0 : ImGuiOldColumnFlags_NoBorder); - //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior - ImGuiOldColumns* columns = window->DC.CurrentColumns; - if (columns != NULL && columns->Count == columns_count && columns->Flags == flags) - return; - - if (columns != NULL) - EndColumns(); - - if (columns_count != 1) - BeginColumns(id, columns_count, flags); -} - -//------------------------------------------------------------------------- - #endif // #ifndef IMGUI_DISABLE From 8da7d3c3e5cd428b4dd7cf277af05c0d889439b7 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 19 Dec 2019 14:50:21 +0100 Subject: [PATCH 003/144] Tables: Initial commit. [Squashed 123+5 commits from tables_wip/] --- imgui.cpp | 141 ++- imgui.h | 153 ++++ imgui_draw.cpp | 9 + imgui_internal.h | 224 ++++- imgui_tables.cpp | 2235 +++++++++++++++++++++++++++++++++++++++++++++ imgui_widgets.cpp | 4 + 6 files changed, 2755 insertions(+), 11 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index ad219eb9..c7be3624 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -947,6 +947,7 @@ ImGuiStyle::ImGuiStyle() FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + CellPadding = ImVec2(4,2); // Padding within a table cell TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). @@ -987,6 +988,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) FrameRounding = ImFloor(FrameRounding * scale_factor); ItemSpacing = ImFloor(ItemSpacing * scale_factor); ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + CellPadding = ImFloor(CellPadding * scale_factor); TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); IndentSpacing = ImFloor(IndentSpacing * scale_factor); ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); @@ -2135,6 +2137,14 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) // the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO) //----------------------------------------------------------------------------- +// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell. +// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous. +static bool GetSkipItemForListClipping() +{ + ImGuiContext& g = *GImGui; + return (g.CurrentTable ? g.CurrentTable->BackupSkipItems : g.CurrentWindow->SkipItems); +} + // Helper to calculate coarse clipping of large list of evenly sized items. // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX @@ -2149,7 +2159,7 @@ void ImGui::CalcListClipping(int items_count, float items_height, int* out_items *out_items_display_end = items_count; return; } - if (window->SkipItems) + if (GetSkipItemForListClipping()) { *out_items_display_start = *out_items_display_end = 0; return; @@ -2185,12 +2195,20 @@ static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height) // The clipper should probably have a 4th step to display the last item in a regular manner. ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + float off_y = pos_y - window->DC.CursorPos.y; window->DC.CursorPos.y = pos_y; window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y); window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. window->DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. if (ImGuiOldColumns* columns = window->DC.CurrentColumns) - columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly + columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly // FIXME-TABLE + if (ImGuiTable* table = g.CurrentTable) + { + if (table->IsInsideRow) + ImGui::TableEndRow(table); + table->RowPosY2 = window->DC.CursorPos.y; + table->RowBgColorCounter += (int)((off_y / line_height) + 0.5f); + } } ImGuiListClipper::ImGuiListClipper() @@ -2212,6 +2230,10 @@ void ImGuiListClipper::Begin(int items_count, float items_height) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + if (ImGuiTable* table = g.CurrentTable) + if (table->IsInsideRow) + ImGui::TableEndRow(table); + StartPosY = window->DC.CursorPos.y; ItemsHeight = items_height; ItemsCount = items_count; @@ -2237,8 +2259,12 @@ bool ImGuiListClipper::Step() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + if (table && table->IsInsideRow) + ImGui::TableEndRow(table); + // Reached end of list - if (DisplayEnd >= ItemsCount || window->SkipItems) + if (DisplayEnd >= ItemsCount || GetSkipItemForListClipping()) { End(); return false; @@ -2266,7 +2292,17 @@ bool ImGuiListClipper::Step() if (StepNo == 1) { IM_ASSERT(ItemsHeight <= 0.0f); - ItemsHeight = window->DC.CursorPos.y - StartPosY; + if (table) + { + const float pos_y1 = table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row + const float pos_y2 = table->RowPosY2; // Using this instead of CursorPos.y to take account of tallest cell. + ItemsHeight = pos_y2 - pos_y1; + window->DC.CursorPos.y = pos_y2; + } + else + { + ItemsHeight = window->DC.CursorPos.y - StartPosY; + } IM_ASSERT(ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); StepNo = 2; } @@ -2405,6 +2441,7 @@ static const ImGuiStyleVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize @@ -2512,6 +2549,9 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; case ImGuiCol_PlotHistogram: return "PlotHistogram"; case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; + case ImGuiCol_TableHeaderBg: return "TableHeaderBg"; + case ImGuiCol_TableRowBg: return "TableRowBg"; + case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; case ImGuiCol_DragDropTarget: return "DragDropTarget"; case ImGuiCol_NavHighlight: return "NavHighlight"; @@ -3998,6 +4038,10 @@ void ImGui::Shutdown(ImGuiContext* context) g.CurrentTabBarStack.clear(); g.ShrinkWidthBuffer.clear(); + g.Tables.Clear(); + g.CurrentTableStack.clear(); + g.DrawChannelsTempMergeBuffer.clear(); + g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); @@ -7374,7 +7418,7 @@ ImVec2 ImGui::GetContentRegionMax() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 mx = window->ContentRegionRect.Max - window->Pos; - if (window->DC.CurrentColumns) + if (window->DC.CurrentColumns || g.CurrentTable) mx.x = window->WorkRect.Max.x - window->Pos.x; return mx; } @@ -7385,7 +7429,7 @@ ImVec2 ImGui::GetContentRegionMaxAbs() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 mx = window->ContentRegionRect.Max; - if (window->DC.CurrentColumns) + if (window->DC.CurrentColumns || g.CurrentTable) mx.x = window->WorkRect.Max.x; return mx; } @@ -10459,6 +10503,23 @@ void ImGui::ShowMetricsWindow(bool* p_open) struct Funcs { + static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n) + { + if (rect_type == TRT_OuterRect) { return table->OuterRect; } + else if (rect_type == TRT_WorkRect) { return table->WorkRect; } + else if (rect_type == TRT_HostClipRect) { return table->HostClipRect; } + else if (rect_type == TRT_InnerClipRect) { return table->InnerClipRect; } + else if (rect_type == TRT_BackgroundClipRect) { return table->BackgroundClipRect; } + else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } + else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } + else if (rect_type == TRT_ColumnsContentHeadersUsed) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate + else if (rect_type == TRT_ColumnsContentHeadersIdeal) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersDesired, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " + else if (rect_type == TRT_ColumnsContentRowsFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthRowsFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " + else if (rect_type == TRT_ColumnsContentRowsUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->MinX + c->ContentWidthRowsUnfrozen, table->InnerClipRect.Max.y); } // " + IM_ASSERT(0); + return ImRect(); + } + static ImRect GetWindowRect(ImGuiWindow* window, int rect_type) { if (rect_type == WRT_OuterRect) { return window->Rect(); } @@ -10500,6 +10561,49 @@ void ImGui::ShowMetricsWindow(bool* p_open) } Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh); Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes); + + Checkbox("Show tables rectangles", &cfg->ShowTablesRects); + SameLine(); + SetNextItemWidth(GetFontSize() * 12); + cfg->ShowTablesRects |= Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count); + if (cfg->ShowTablesRects && g.NavWindow != NULL) + { + for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) + { + ImGuiTable* table = g.Tables.GetByIndex(table_n); + if (table->LastFrameActive < g.FrameCount - 1 || table->OuterWindow != g.NavWindow) + continue; + + BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + Indent(); + for (int rect_n = 0; rect_n < TRT_Count; rect_n++) + { + if (rect_n >= TRT_ColumnsRect) + { + if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect) + continue; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImRect r = Funcs::GetTableRect(table, rect_n, column_n); + Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + } + } + else + { + ImRect r = Funcs::GetTableRect(table, rect_n, -1); + Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, ~0, 2.0f); + } + } + Unindent(); + } + } + TreePop(); } @@ -10533,7 +10637,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) } // Details for Tables - IM_UNUSED(trt_rects_names); #ifdef IMGUI_HAS_TABLE if (TreeNode("Tables", "Tables (%d)", g.Tables.GetSize())) { @@ -10584,8 +10687,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) #ifdef IMGUI_HAS_TABLE if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) { - for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) - DebugNodeTableSettings(settings); + //for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + // DebugNodeTableSettings(settings); TreePop(); } #endif // #ifdef IMGUI_HAS_TABLE @@ -10664,11 +10767,29 @@ void ImGui::ShowMetricsWindow(bool* p_open) #ifdef IMGUI_HAS_TABLE // Overlay: Display Tables Rectangles - if (show_tables_rects) + if (cfg->ShowTablesRects) { for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) { ImGuiTable* table = g.Tables.GetByIndex(table_n); + if (table->LastFrameActive < g.FrameCount - 1) + continue; + ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow); + if (cfg->ShowTablesRectsType >= TRT_ColumnsRect) + { + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); + ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); + float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; + draw_list->AddRect(r.Min, r.Max, col, 0.0f, ~0, thickness); + } + } + else + { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)); + } } } #endif // #ifdef IMGUI_HAS_TABLE diff --git a/imgui.h b/imgui.h index d1d42b3c..69ef0d42 100644 --- a/imgui.h +++ b/imgui.h @@ -33,6 +33,8 @@ Index of this file: // Draw List API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) // Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// FIXME-TABLE: Add ImGuiTableSortSpecsColumn and ImGuiTableSortSpecs in "Misc data structures" section above (we don't do it right now to facilitate merging various branches) + */ #pragma once @@ -136,6 +138,8 @@ struct ImGuiPayload; // User data payload for drag and drop opera struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) struct ImGuiStorage; // Helper for key->value storage struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTableSortSpecs; // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +struct ImGuiTableSortSpecsColumn; // Sorting specification for one column of a table struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") @@ -151,6 +155,7 @@ typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input identifier for navigation typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier +typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect(), AddRectFilled() etc. typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList @@ -170,6 +175,9 @@ typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: f typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() typedef int ImGuiTabItemFlags; // -> enum ImGuiTabItemFlags_ // Flags: for BeginTabItem() +typedef int ImGuiTableFlags; // -> enum ImGuiTableFlags_ // Flags: For BeginTable() +typedef int ImGuiTableColumnFlags; // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn() +typedef int ImGuiTableRowFlags; // -> enum ImGuiTableRowFlags_ // Flags: For TableNextRow() typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader() typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin(), BeginChild() @@ -657,6 +665,35 @@ namespace ImGui IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column IMGUI_API int GetColumnsCount(); + // Tables + // [ALPHA API] API will evolve! (FIXME-TABLE) + // - Full-featured replacement for old Columns API + // - In most situations you can use TableNextRow() + TableSetColumnIndex() to populate a table. + // - If you are using tables as a sort of grid, populating every columns with the same type of contents, + // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). + #define IMGUI_HAS_TABLE 1 + IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); + IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! + IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. + IMGUI_API bool TableNextCell(); // append into the next column (next column, or next row if currently in last column). Return true if column is visible. + IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. + IMGUI_API int TableGetColumnIndex(); // return current column index. + IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Use pass -1 to use current column. + IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Use pass -1 to use current column. + IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Use pass -1 to use current column. + // Tables: Headers & Columns declaration + // - Use TableSetupColumn() to specify resizing policy, default width, name, id, specific flags etc. + // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu + // - Use TableAutoHeaders() to submit the whole header row, otherwise you may treat the header row as a regular row, manually call TableHeader() and other widgets. + // - Headers are required to perform some interactions: reordering, sorting, context menu // FIXME-TABLES: remove context from this list! + IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); + IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu + IMGUI_API void TableHeader(const char* label); // submit one header cell manually. + // Tables: Sorting + // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. + // - Read ->SpecsChanged to tell if the specs have changed since last call. + IMGUI_API const ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + // 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! @@ -967,6 +1004,85 @@ enum ImGuiTabItemFlags_ ImGuiTabItemFlags_Trailing = 1 << 7 // Enforce the tab position to the right of the tab bar (before the scrolling buttons) }; +// Flags for ImGui::BeginTable() +enum ImGuiTableFlags_ +{ + // Features + ImGuiTableFlags_None = 0, + ImGuiTableFlags_Resizable = 1 << 0, // Allow resizing columns. + ImGuiTableFlags_Reorderable = 1 << 1, // Allow reordering columns (need calling TableSetupColumn() + TableAutoHeaders() or TableHeaders() to display headers) + ImGuiTableFlags_Hideable = 1 << 2, // Allow hiding columns (with right-click on header) (FIXME-TABLE: allow without headers). + ImGuiTableFlags_Sortable = 1 << 3, // Allow sorting on one column (sort_specs_count will always be == 1). Call TableGetSortSpecs() to obtain sort specs. + ImGuiTableFlags_MultiSortable = 1 << 4, // Allow sorting on multiple columns by holding Shift (sort_specs_count may be > 1). Call TableGetSortSpecs() to obtain sort specs. + ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. + // Decoration + ImGuiTableFlags_RowBg = 1 << 6, // Use ImGuiCol_TableRowBg and ImGuiCol_TableRowBgAlt colors behind each rows. + ImGuiTableFlags_BordersOuter = 1 << 7, // Draw outer borders. + ImGuiTableFlags_BordersV = 1 << 8, // Draw vertical borders between columns. + ImGuiTableFlags_BordersH = 1 << 9, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersFullHeight = 1 << 10, // Borders covers all lines even when Headers are being used, allow resizing all rows. + ImGuiTableFlags_Borders = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersH, + // Padding, Sizing + ImGuiTableFlags_NoClipX = 1 << 12, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items with be able to overflow) + ImGuiTableFlags_SizingPolicyStretchX = 1 << 13, // (Default if ScrollX is off) Columns will default to use ImGuiTableColumnFlags_WidthStretch. Fit all columns within available width. Fixed and Weighted columns allowed. + ImGuiTableFlags_SizingPolicyFixedX = 1 << 14, // (Default if ScrollX is on) Columns will default to use ImGuiTableColumnFlags_WidthFixed or WidthAuto. Enlarge as needed: enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rect. Only Fixed columns allowed. Weighted columns will calculate their width assuming no scrolling. + ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribute to automatic width calculation for every columns. + ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + // Scrolling + ImGuiTableFlags_ScrollX = 1 << 17, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 18, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, + ImGuiTableFlags_ScrollFreezeRowsShift_ = 19, // We can lock 1 to 3 rows (starting from the top). Encode each of those values as dedicated flags. + ImGuiTableFlags_ScrollFreezeTopRow = 1 << ImGuiTableFlags_ScrollFreezeRowsShift_, + ImGuiTableFlags_ScrollFreeze2Rows = 2 << ImGuiTableFlags_ScrollFreezeRowsShift_, + ImGuiTableFlags_ScrollFreeze3Rows = 3 << ImGuiTableFlags_ScrollFreezeRowsShift_, + ImGuiTableFlags_ScrollFreezeColumnsShift_ = 21, // We can lock 1 to 3 columns (starting from the left). Encode each of those values as dedicated flags. + ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << ImGuiTableFlags_ScrollFreezeColumnsShift_, + ImGuiTableFlags_ScrollFreeze2Columns = 2 << ImGuiTableFlags_ScrollFreezeColumnsShift_, + ImGuiTableFlags_ScrollFreeze3Columns = 3 << ImGuiTableFlags_ScrollFreezeColumnsShift_, + + // Combinations and masks + ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX, + ImGuiTableFlags_ScrollFreezeRowsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeRowsShift_, + ImGuiTableFlags_ScrollFreezeColumnsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeColumnsShift_ +}; + +// Flags for ImGui::TableSetupColumn() +// FIXME-TABLE: Rename to ImGuiColumns_*, stick old columns api flags in there under an obsolete api block +enum ImGuiTableColumnFlags_ +{ + ImGuiTableColumnFlags_None = 0, + ImGuiTableColumnFlags_DefaultHide = 1 << 0, // Default as a hidden column. + ImGuiTableColumnFlags_DefaultSort = 1 << 1, // Default as a sorting column. + ImGuiTableColumnFlags_WidthFixed = 1 << 2, // Column will keep a fixed size, preferable with horizontal scrolling enabled (default if table sizing policy is SizingPolicyFixedX). + ImGuiTableColumnFlags_WidthStretch = 1 << 3, // Column will stretch, preferable with horizontal scrolling disabled (default if table sizing policy is SizingPolicyStretchX). + ImGuiTableColumnFlags_WidthAlwaysAutoResize = 1 << 4, // Column will keep resizing based on submitted contents (with a one frame delay) == Fixed with auto resize + ImGuiTableColumnFlags_NoResize = 1 << 5, // Disable manual resizing. + ImGuiTableColumnFlags_NoClipX = 1 << 6, // Disable clipping for this column (all NoClipX columns will render in a same draw command). + ImGuiTableColumnFlags_NoSort = 1 << 7, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). + ImGuiTableColumnFlags_NoSortAscending = 1 << 8, // Disable ability to sort in the ascending direction. + ImGuiTableColumnFlags_NoSortDescending = 1 << 9, // Disable ability to sort in the descending direction. + ImGuiTableColumnFlags_NoHide = 1 << 10, // Disable hiding this column. + ImGuiTableColumnFlags_NoHeaderWidth = 1 << 11, // Header width don't contribute to automatic column width. + ImGuiTableColumnFlags_PreferSortAscending = 1 << 12, // Make the initial sort direction Ascending when first sorting on this column (default). + ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. + //ImGuiTableColumnFlags_AlignLeft = 1 << 14, + //ImGuiTableColumnFlags_AlignCenter = 1 << 15, + //ImGuiTableColumnFlags_AlignRight = 1 << 16, + + // Combinations and masks + ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, + ImGuiTableColumnFlags_NoDirectResize_ = 1 << 20 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) + //ImGuiTableColumnFlags_AlignMask_ = ImGuiTableColumnFlags_AlignLeft | ImGuiTableColumnFlags_AlignCenter | ImGuiTableColumnFlags_AlignRight +}; + +// Flags for ImGui::TableNextRow() +enum ImGuiTableRowFlags_ +{ + ImGuiTableRowFlags_None = 0, + ImGuiTableRowFlags_Headers = 1 << 0 // Identify header row (set default background color + width of its contents accounted different for auto column width) +}; + // Flags for ImGui::IsWindowFocused() enum ImGuiFocusedFlags_ { @@ -1044,6 +1160,14 @@ enum ImGuiDir_ ImGuiDir_COUNT }; +// A sorting direction +enum ImGuiSortDirection_ +{ + ImGuiSortDirection_None = 0, + ImGuiSortDirection_Ascending = 1, // Ascending = 0->9, A->Z etc. + ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. +}; + // User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array enum ImGuiKey_ { @@ -1188,6 +1312,9 @@ enum ImGuiCol_ ImGuiCol_PlotLinesHovered, ImGuiCol_PlotHistogram, ImGuiCol_PlotHistogramHovered, + ImGuiCol_TableHeaderBg, // Table header background + ImGuiCol_TableRowBg, // Table row background (even rows) + ImGuiCol_TableRowBgAlt, // Table row background (odd rows) ImGuiCol_TextSelectedBg, ImGuiCol_DragDropTarget, ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item @@ -1228,6 +1355,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_CellPadding, // ImVec2 CellPadding ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding ImGuiStyleVar_GrabMinSize, // float GrabMinSize @@ -1464,6 +1592,7 @@ struct ImGuiStyle float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 CellPadding; // Padding within a table cell ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). @@ -1700,6 +1829,30 @@ struct ImGuiPayload bool IsDelivery() const { return Delivery; } }; +// Sorting specification for one column of a table (sizeof == 8 bytes) +struct ImGuiTableSortSpecsColumn +{ + ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) + ImU8 ColumnIndex; // Index of the column + ImU8 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) + ImS8 SortSign; // +1 or -1 (you can use this or SortDirection, whichever is more convenient for your sort function) + ImS8 SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more convenient for your sort function) + + ImGuiTableSortSpecsColumn() { ColumnUserID = 0; ColumnIndex = 0; SortOrder = 0; SortSign = +1; SortDirection = ImGuiSortDirection_Ascending; } +}; + +// Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +// Obtained by calling TableGetSortSpecs() +struct ImGuiTableSortSpecs +{ + const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. + int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. + bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. + ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. + + ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsChanged = false; ColumnsMask = 0x00; } +}; + //----------------------------------------------------------------------------- // Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) // Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 93eb23f2..e915dd76 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -222,6 +222,9 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); @@ -277,6 +280,9 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.27f, 0.27f, 0.38f, 1.00f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; @@ -333,6 +339,9 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; diff --git a/imgui_internal.h b/imgui_internal.h index 33cc991e..5ec83845 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -111,6 +111,10 @@ struct ImGuiStackSizes; // Storage of stack sizes for debugging/asse struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabItem; // Storage for a tab item (within a tab bar) +struct ImGuiTable; // Storage for a table +struct ImGuiTableColumn; // Storage for one column of a table +struct ImGuiTableSettings; // Storage for a table .ini settings +struct ImGuiTableColumnsSettings; // Storage for a column .ini settings struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) @@ -264,6 +268,7 @@ IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); // Helpers: Bit manipulation static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } // Helpers: String, Formatting @@ -1329,6 +1334,12 @@ struct ImGuiContext ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly, ImGuiPayload only holds pointer+size unsigned char DragDropPayloadBufLocal[16]; // Local buffer for small payloads + // Table + ImGuiTable* CurrentTable; + ImPool Tables; + ImVector CurrentTableStack; + ImVector DrawChannelsTempMergeBuffer; + // Tab bars ImGuiTabBar* CurrentTabBar; ImPool TabBars; @@ -1366,6 +1377,7 @@ struct ImGuiContext ImGuiTextBuffer SettingsIniData; // In memory .ini settings ImVector SettingsHandlers; // List of .ini settings handlers ImChunkStream SettingsWindows; // ImGuiWindow .ini settings entries + ImChunkStream SettingsTables; // ImGuiTable .ini settings entries ImVector Hooks; // Hooks for extensions (e.g. test engine) // Capture/Logging @@ -1496,6 +1508,7 @@ struct ImGuiContext DragDropHoldJustPressedId = 0; memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + CurrentTable = NULL; CurrentTabBar = NULL; LastValidMousePos = ImVec2(0.0f, 0.0f); @@ -1581,6 +1594,7 @@ struct IMGUI_API ImGuiWindowTempData ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiOldColumns* CurrentColumns; // Current columns set + ImGuiTable* CurrentTable; // Current table set ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase as assigned via FocusableItemRegister() (FIXME-NAV: Needs redesign) @@ -1805,7 +1819,185 @@ struct ImGuiTabBar //----------------------------------------------------------------------------- #ifdef IMGUI_HAS_TABLE -// + +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code +#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. + +// [Internal] sizeof() ~ 96 +struct ImGuiTableColumn +{ + ImGuiID UserID; // Optional, value passed to TableSetupColumn() + ImGuiTableColumnFlags FlagsIn; // Flags as input by user. See ImGuiTableColumnFlags_ + ImGuiTableColumnFlags Flags; // Effective flags. See ImGuiTableColumnFlags_ + float ResizeWeight; // ~1.0f. Master width data when (Flags & _WidthStretch) + float MinX; // Absolute positions + float MaxX; + float WidthRequested; // Master width data when !(Flags & _WidthStretch) + float WidthGiven; // == (MaxX - MinX). FIXME-TABLE: Store all persistent width in multiple of FontSize? + float StartXRows; // Start position for the frame, currently ~(MinX + CellPaddingX) + float StartXHeaders; + ImS16 ContentWidthRowsFrozen; // Contents width. Because freezing is non correlated from headers we need all 4 variants (ImDrawCmd merging uses different data than alignment code). + ImS16 ContentWidthRowsUnfrozen; // (encoded as ImS16 because we actually rarely use those width) + ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls + ImS16 ContentWidthHeadersDesired; + float ContentMaxPosRowsFrozen; // Submitted contents absolute maximum position, from which we can infer width. + float ContentMaxPosRowsUnfrozen; // (kept as float because we need to manipulate those between each cell change) + float ContentMaxPosHeadersUsed; + float ContentMaxPosHeadersDesired; + ImRect ClipRect; + ImS16 NameOffset; // Offset into parent ColumnsName[] + bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. + bool NextIsActive; + ImS8 IndexDisplayOrder; // Index within DisplayOrder[] (column may be reordered by users) + ImS8 IndexWithinActiveSet; // Index within active set (<= IndexOrder) + ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] + ImS8 DrawChannelRowsBeforeFreeze; + ImS8 DrawChannelRowsAfterFreeze; + ImS8 PrevActiveColumn; // Index of prev active column within Columns[], -1 if first active column + ImS8 NextActiveColumn; // Index of next active column within Columns[], -1 if last active column + ImS8 AutoFitFrames; + ImS8 SortOrder; // -1: Not sorting on this column + ImS8 SortDirection; // enum ImGuiSortDirection_ + + ImGuiTableColumn() + { + memset(this, 0, sizeof(*this)); + ResizeWeight = 1.0f; + WidthRequested = WidthGiven = -1.0f; + NameOffset = -1; + IsActive = NextIsActive = true; + IndexDisplayOrder = IndexWithinActiveSet = -1; + DrawChannelCurrent = DrawChannelRowsBeforeFreeze = DrawChannelRowsAfterFreeze = -1; + PrevActiveColumn = NextActiveColumn = -1; + AutoFitFrames = 3; + SortOrder = -1; + SortDirection = ImGuiSortDirection_Ascending; + } +}; + +// FIXME-OPT: Since CountColumns is invariant, we could use a single alloc for ImGuiTable + the three vectors it is carrying. +struct ImGuiTable +{ + ImGuiID ID; + ImGuiTableFlags Flags; + ImVector Columns; + ImVector DisplayOrder; // Store display order of columns (when not reordered, the values are 0...Count-1) + ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data + ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map + ImGuiTableFlags SettingsSaveFlags; // Pre-compute which data we are going to save into the .ini file (e.g. when order is not altered we won't save order) + int SettingsOffset; // Offset in g.SettingsTables + int LastFrameActive; + int ColumnsCount; // Number of columns declared in BeginTable() + int ColumnsActiveCount; // Number of non-hidden columns (<= ColumnsCount) + int CurrentColumn; + int CurrentRow; + float RowPosY1; + float RowPosY2; + float RowTextBaseline; + ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ + ImGuiTableRowFlags LastRowFlags : 16; + int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper) + ImU32 RowBgColor; // Request for current row background color + ImU32 BorderOuterColor; + ImU32 BorderInnerColor; + float BorderX1; + float BorderX2; + float CellPaddingX1; // Padding from each borders + float CellPaddingX2; + float CellPaddingY; + float CellSpacingX; // Spacing between non-bordered cells + float LastOuterHeight; // Outer height from last frame + float LastFirstRowHeight; // Height of first row from last frame + float ColumnsTotalWidth; + float InnerWidth; + ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). + ImRect WorkRect; + ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. + ImRect InnerClipRect; + ImRect BackgroundClipRect; // We use this to cpu-clip cell background color fill + ImGuiWindow* OuterWindow; // Parent window for the table + ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) + ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names + ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (could be stored outside?) + ImVector SortSpecsData; // FIXME-OPT: Fixed-size array / small-vector pattern, optimize for single sort spec + ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() + ImS8 SortSpecsCount; + ImS8 DeclColumnsCount; // Count calls to TableSetupColumn() + ImS8 HoveredColumnBody; // [DEBUG] Unlike HoveredColumnBorder this doesn't fulfill all Hovering rules properly. Used for debugging/tools for now. + ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). + ImS8 ResizedColumn; // Index of column being resized. + ImS8 LastResizedColumn; + ImS8 ReorderColumn; // Index of column being reordered. (not cleared) + ImS8 ReorderColumnDir; // -1 or +1 + ImS8 RightMostActiveColumn; // Index of right-most non-hidden column. + ImS8 LeftMostStretchedColumnDisplayOrder; // Display order of left-most stretched column. + ImS8 ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot + ImS8 DummyDrawChannel; // Redirect non-visible columns here. + ImS8 FreezeRowsRequest; // Requested frozen rows count + ImS8 FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) + ImS8 FreezeColumnsRequest; // Requested frozen columns count + ImS8 FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) + bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. + bool IsInsideRow; // Set if inside TableBeginRow()/TableEndRow(). + bool IsFirstFrame; + bool IsSortSpecsDirty; + bool IsUsingHeaders; // Set if the first row had the ImGuiTableRowFlags_Headers flag. + bool IsContextPopupOpen; + bool IsSettingsRequestLoad; + bool IsSettingsLoaded; + bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. + bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) + bool IsResetDisplayOrderRequest; + bool IsFreezeRowsPassed; // Set when we got past the frozen row (the first one). + bool BackupSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis + ImRect BackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImVec2 BackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + + ImGuiTable() + { + memset(this, 0, sizeof(*this)); + SettingsOffset = -1; + LastFrameActive = -1; + LastResizedColumn = -1; + ContextPopupColumn = -1; + ReorderColumn = -1; + } +}; + +// sizeof() ~ 12 +struct ImGuiTableColumnSettings +{ + float WidthOrWeight; + ImGuiID UserID; + ImS8 Index; + ImS8 DisplayOrder; + ImS8 SortOrder; + ImS8 SortDirection : 7; + ImU8 Visible : 1; // This is called Active in ImGuiTableColumn, in .ini file we call it Visible. + + ImGuiTableColumnSettings() + { + WidthOrWeight = 0.0f; + UserID = 0; + Index = -1; + DisplayOrder = SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + Visible = 1; + } +}; + +// This is designed to be stored in a single ImChunkStream (1 header followed by N ImGuiTableColumnSettings, etc.) +struct ImGuiTableSettings +{ + ImGuiID ID; // Set to 0 to invalidate/delete the setting + ImGuiTableFlags SaveFlags; + ImS8 ColumnsCount; + ImS8 ColumnsCountMax; + + ImGuiTableSettings() { memset(this, 0, sizeof(*this)); } + ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } +}; + #endif // #ifdef IMGUI_HAS_TABLE //----------------------------------------------------------------------------- @@ -1977,6 +2169,35 @@ namespace ImGui IMGUI_API float GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm); IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); + // Tables + //IMGUI_API int GetTableColumnNo(); + //IMGUI_API bool SetTableColumnNo(int column_n); + //IMGUI_API int GetTableLineNo(); + IMGUI_API void TableBeginInitVisibility(ImGuiTable* table); + IMGUI_API void TableBeginInitDrawChannels(ImGuiTable* table); + IMGUI_API void TableUpdateLayout(ImGuiTable* table); + IMGUI_API void TableUpdateBorders(ImGuiTable* table); + IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); + IMGUI_API void TableDrawBorders(ImGuiTable* table); + IMGUI_API void TableDrawMergeChannels(ImGuiTable* table); + IMGUI_API void TableDrawContextMenu(ImGuiTable* table, int column_n); + IMGUI_API void TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* column, bool add_to_existing_sort_orders); + IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); + IMGUI_API void TableBeginRow(ImGuiTable* table); + IMGUI_API void TableEndRow(ImGuiTable* table); + IMGUI_API void TableBeginCell(ImGuiTable* table, int column_no); + IMGUI_API void TableEndCell(ImGuiTable* table); + IMGUI_API ImRect TableGetCellRect(); + IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_no); + IMGUI_API void PushTableBackground(); + IMGUI_API void PopTableBackground(); + IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableSaveSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableFindSettings(ImGuiTable* table); + IMGUI_API void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); + IMGUI_API void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); + IMGUI_API void TableSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); + // Tab Bars IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); @@ -2094,6 +2315,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImGuiWindow* window, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); + IMGUI_API void DebugNodeTable(ImGuiTable* table); IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 67b625da..657ccad4 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -61,6 +61,2241 @@ // [SECTION] Widgets: BeginTable, EndTable, etc. //----------------------------------------------------------------------------- +// Typical call flow: (root level is public API): +// - BeginTable() user begin into a table +// - BeginChild() - (if ScrollX/ScrollY is set) +// - TableBeginInitVisibility() - lock columns visibility +// - TableBeginInitDrawChannels() - setup ImDrawList channels +// - TableSetupColumn() user submit columns details (optional) +// - TableAutoHeaders() or TableHeader() user submit a headers row (optional) +// - TableSortSpecsClickColumn() +// - TableGetSortSpecs() user queries updated sort specs (optional) +// - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() +// - TableUpdateLayout() - called by the FIRST call to TableNextRow() +// - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission +// - TableDrawContextMenu() - draw right-click context menu +// - [...] user emit contents +// - EndTable() user ends the table +// - TableDrawBorders() - draw outer borders, inner vertical borders +// - TableDrawMergeChannels() - merge draw channels if clipping isn't required +// - TableSetColumnWidth() - apply resizing width +// - TableUpdateColumnsWeightFromWidth() +// - EndChild() - (if ScrollX/ScrollY is set) + +// Configuration +static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. +static const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f; // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. + +// Helper +inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) +{ + // Adjust flags: set default sizing policy + if ((flags & ImGuiTableFlags_SizingPolicyMaskX_) == 0) + flags |= (flags & ImGuiTableFlags_ScrollX) ? ImGuiTableFlags_SizingPolicyFixedX : ImGuiTableFlags_SizingPolicyStretchX; + + // Adjust flags: MultiSortable automatically enable Sortable + if (flags & ImGuiTableFlags_MultiSortable) + flags |= ImGuiTableFlags_Sortable; + + // Adjust flags: disable saved settings if there's nothing to save + if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0) + flags |= ImGuiTableFlags_NoSavedSettings; + + // Adjust flags: enforce borders when resizable + if (flags & ImGuiTableFlags_Resizable) + flags |= ImGuiTableFlags_BordersV; + + // Adjust flags: disable top rows freezing if there's no scrolling + if ((flags & ImGuiTableFlags_ScrollX) == 0) + flags &= ~ImGuiTableFlags_ScrollFreezeColumnsMask_; + if ((flags & ImGuiTableFlags_ScrollY) == 0) + flags &= ~ImGuiTableFlags_ScrollFreezeRowsMask_; + + // Adjust flags: disable NoHostExtendY if we have any scrolling going on + if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) + flags &= ~ImGuiTableFlags_NoHostExtendY; + + // Adjust flags: we don't support NoClipX with (FreezeColumns > 0), we could with some work but it doesn't appear to be worth the effort + if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) + flags &= ~ImGuiTableFlags_NoClipX; + + return flags; +} + +// About 'outer_size': +// The meaning of outer_size needs to differ slightly depending of if we are using ScrollX/ScrollY flags. +// With ScrollX/ScrollY: using a child window for scrolling: +// - outer_size.y < 0.0f -> bottom align +// - outer_size.y = 0.0f -> bottom align: consistent with BeginChild(), best to preserve (0,0) default arg +// - outer_size.y > 0.0f -> fixed child height +// Without scrolling, we output table directly in parent window: +// - outer_size.y < 0.0f -> bottom align (will auto extend, unless NoHostExtendV is set) +// - outer_size.y = 0.0f -> zero minimum height (will auto extend, unless NoHostExtendV is set) +// - outer_size.y > 0.0f -> minimum height (will auto extend, unless NoHostExtendV is set) +// About: 'inner_width': +// With ScrollX: +// - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird +// - inner_width = 0.0f -> auto enlarge: *only* fixed size columns, which will take space they need (proportional columns becomes fixed columns) <-- desired default :( +// - inner_width > 0.0f -> fit in known width: fixed column take space they need if possible (otherwise shrink down), proportional columns share remaining space. +// Without ScrollX: +// - inner_width < 0.0f -> fit in known width (right align from outer_size.x) <-- desired default +// - inner_width = 0.0f -> auto enlarge: will emit contents size in parent window +// - inner_width > 0.0f -> fit in known width (bypass outer_size.x, permitted but not useful, should instead alter outer_width) +// FIXME-TABLE: This is currently not very useful. +// FIXME-TABLE: Replace enlarge vs fixed width by a flag. +// Even if not really useful, we allow 'inner_scroll_width < outer_size.x' for consistency and to facilitate understanding of what the value does. +bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* outer_window = GetCurrentWindow(); + IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS && "Only 0..63 columns allowed!"); + if (flags & ImGuiTableFlags_ScrollX) + IM_ASSERT(inner_width >= 0.0f); + ImGuiID id = GetID(str_id); + + const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; + const ImVec2 avail_size = GetContentRegionAvail(); + ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); + ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); + + // If an outer size is specified ahead we will be able to early out when not visible, + // The exact rules here can evolve. + if (use_child_window && IsClippedEx(outer_rect, id, false)) + { + ItemSize(outer_rect); + return false; + } + + flags = TableFixFlags(flags); + if (outer_window->Flags & ImGuiWindowFlags_NoSavedSettings) + flags |= ImGuiTableFlags_NoSavedSettings; + + // Acquire storage for the table + ImGuiTable* table = g.Tables.GetOrAddByKey(id); + const ImGuiTableFlags table_last_flags = table->Flags; + table->ID = id; + table->Flags = flags; + table->IsFirstFrame = (table->LastFrameActive == -1); + table->LastFrameActive = g.FrameCount; + table->OuterWindow = table->InnerWindow = outer_window; + table->ColumnsCount = columns_count; + table->ColumnsNames.Buf.resize(0); + table->IsLayoutLocked = false; + table->InnerWidth = inner_width; + table->OuterRect = outer_rect; + table->WorkRect = outer_rect; + + if (use_child_window) + { + // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) + ImVec2 override_content_size(FLT_MAX, FLT_MAX); + if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) + override_content_size.y = FLT_MIN; + + // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and never lead to any scrolling) + // We don't handle inner_width < 0.0f, we could potentially use it to right-align based on the right side of the child window work rect, + // which would require knowing ahead if we are going to have decoration taking horizontal spaces (typically a vertical scrollbar). + if (inner_width != 0.0f) + override_content_size.x = inner_width; + + if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) + SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f)); + + // Create scrolling region (without border = zero window padding) + ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(str_id, id, table->OuterRect.GetSize(), false, child_flags); + table->InnerWindow = g.CurrentWindow; + table->WorkRect = table->InnerWindow->WorkRect; + table->OuterRect = table->InnerWindow->Rect(); + } + else + { + // WorkRect.Max will grow as we append contents. + PushID(id); + } + + const bool has_cell_padding_x = (flags & (ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) != 0; + ImGuiWindow* inner_window = table->InnerWindow; + table->CurrentColumn = -1; + table->CurrentRow = -1; + table->RowBgColorCounter = 0; + table->LastRowFlags = ImGuiTableRowFlags_None; + + table->CellPaddingX1 = has_cell_padding_x ? g.Style.CellPadding.x + 1.0f : 0.0f; + table->CellPaddingX2 = has_cell_padding_x ? g.Style.CellPadding.x : 0.0f; + table->CellPaddingY = g.Style.CellPadding.y; + table->CellSpacingX = has_cell_padding_x ? 0.0f : g.Style.CellPadding.x; + + table->HostClipRect = inner_window->ClipRect; + table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; + table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width + table->InnerClipRect.ClipWith(table->HostClipRect); + table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? table->WorkRect.Max.y : inner_window->ClipRect.Max.y; + table->BackgroundClipRect = table->InnerClipRect; + table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow + table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() + table->FreezeRowsRequest = (ImS8)((flags & ImGuiTableFlags_ScrollFreezeRowsMask_) >> ImGuiTableFlags_ScrollFreezeRowsShift_); + table->FreezeRowsCount = (inner_window->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; + table->FreezeColumnsRequest = (ImS8)((flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) >> ImGuiTableFlags_ScrollFreezeColumnsShift_); + table->FreezeColumnsCount = (inner_window->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; + table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); + table->DeclColumnsCount = 0; + table->LastResizedColumn = table->ResizedColumn; + table->HoveredColumnBody = -1; + table->HoveredColumnBorder = -1; + table->RightMostActiveColumn = -1; + table->LeftMostStretchedColumnDisplayOrder = -1; + table->IsFirstFrame = false; + + // FIXME-TABLE FIXME-STYLE: Using opaque colors facilitate overlapping elements of the grid + //table->BorderOuterColor = GetColorU32(ImGuiCol_Separator, 1.00f); + //table->BorderInnerColor = GetColorU32(ImGuiCol_Separator, 0.60f); + table->BorderOuterColor = GetColorU32(ImVec4(0.31f, 0.31f, 0.35f, 1.00f)); + table->BorderInnerColor = GetColorU32(ImVec4(0.23f, 0.23f, 0.25f, 1.00f)); + //table->BorderOuterColor = IM_COL32(255, 0, 0, 255); + //table->BorderInnerColor = IM_COL32(255, 255, 0, 255); + table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); + table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); + + // Make table current + g.CurrentTableStack.push_back(ImGuiPtrOrIndex(g.Tables.GetIndex(table))); + g.CurrentTable = table; + outer_window->DC.CurrentTable = table; + if ((table_last_flags & ImGuiTableFlags_Reorderable) && !(flags & ImGuiTableFlags_Reorderable)) + table->IsResetDisplayOrderRequest = true; + + // Clear data if columns count changed + if (table->Columns.Size != 0 && table->Columns.Size != columns_count) + { + table->Columns.resize(0); + table->DisplayOrder.resize(0); + } + + // Setup default columns state + if (table->Columns.Size == 0) + { + table->IsFirstFrame = true; + table->IsSortSpecsDirty = true; + table->Columns.reserve(columns_count); + table->DisplayOrder.reserve(columns_count); + for (int n = 0; n < columns_count; n++) + { + ImGuiTableColumn column; + column.IndexDisplayOrder = (ImS8)n; + table->Columns.push_back(column); + table->DisplayOrder.push_back(column.IndexDisplayOrder); + } + } + + // Load settings + if (table->IsFirstFrame || table->IsSettingsRequestLoad) + TableLoadSettings(table); + + // Handle reordering request + // Note: we don't clear ReorderColumn after handling the request. + if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) + { + IM_ASSERT(table->ReorderColumnDir == -1 || table->ReorderColumnDir == +1); + IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); + ImGuiTableColumn* dragged_column = &table->Columns[table->ReorderColumn]; + ImGuiTableColumn* target_column = &table->Columns[(table->ReorderColumnDir == -1) ? dragged_column->PrevActiveColumn : dragged_column->NextActiveColumn]; + ImSwap(table->DisplayOrder[dragged_column->IndexDisplayOrder], table->DisplayOrder[target_column->IndexDisplayOrder]); + ImSwap(dragged_column->IndexDisplayOrder, target_column->IndexDisplayOrder); + table->ReorderColumnDir = 0; + table->IsSettingsDirty = true; + } + + // Handle display order reset request + if (table->IsResetDisplayOrderRequest) + { + for (int n = 0; n < columns_count; n++) + table->DisplayOrder[n] = table->Columns[n].IndexDisplayOrder = (ImU8)n; + table->IsResetDisplayOrderRequest = false; + table->IsSettingsDirty = true; + } + + TableBeginInitVisibility(table); + TableBeginInitDrawChannels(table); + + // Grab a copy of window fields we will modify + table->BackupSkipItems = inner_window->SkipItems; + table->BackupWorkRect = inner_window->WorkRect; + table->BackupCursorMaxPos = inner_window->DC.CursorMaxPos; + + if (flags & ImGuiTableFlags_NoClipX) + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 1); + else + inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); + + return true; +} + +void ImGui::TableBeginInitVisibility(ImGuiTable* table) +{ + // Setup and lock Active state and order + table->ColumnsActiveCount = 0; + table->IsDefaultDisplayOrder = true; + ImGuiTableColumn* last_active_column = NULL; + bool want_column_auto_fit = false; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + const int column_n = table->DisplayOrder[order_n]; + if (column_n != order_n) + table->IsDefaultDisplayOrder = false; + ImGuiTableColumn* column = &table->Columns[column_n]; + column->NameOffset = -1; + if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) + column->NextIsActive = true; + if (column->IsActive != column->NextIsActive) + { + column->IsActive = column->NextIsActive; + table->IsSettingsDirty = true; + if (!column->IsActive && column->SortOrder != -1) + table->IsSortSpecsDirty = true; + } + if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_MultiSortable)) + table->IsSortSpecsDirty = true; + if (column->AutoFitFrames > 0) + want_column_auto_fit = true; + + ImU64 index_mask = (ImU64)1 << column_n; + ImU64 display_order_mask = (ImU64)1 << column->IndexDisplayOrder; + if (column->IsActive) + { + column->PrevActiveColumn = column->NextActiveColumn = -1; + if (last_active_column) + { + last_active_column->NextActiveColumn = (ImS8)column_n; + column->PrevActiveColumn = (ImS8)table->Columns.index_from_ptr(last_active_column); + } + column->IndexWithinActiveSet = (ImS8)table->ColumnsActiveCount; + table->ColumnsActiveCount++; + table->ActiveMaskByIndex |= index_mask; + table->ActiveMaskByDisplayOrder |= display_order_mask; + last_active_column = column; + } + else + { + column->IndexWithinActiveSet = -1; + table->ActiveMaskByIndex &= ~index_mask; + table->ActiveMaskByDisplayOrder &= ~display_order_mask; + } + IM_ASSERT(column->IndexWithinActiveSet <= column->IndexDisplayOrder); + } + table->RightMostActiveColumn = (ImS8)(last_active_column ? table->Columns.index_from_ptr(last_active_column) : -1); + + // Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid + // the column fitting to wait until the first visible frame of the child container (may or not be a good thing). + if (want_column_auto_fit && table->OuterWindow != table->InnerWindow) + table->InnerWindow->SkipItems = false; +} + +void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) +{ + // Allocate draw channels. + // - We allocate them following the storage order instead of the display order so reordering won't needlessly increase overall dormant memory cost + // - We isolate headers draw commands in their own channels instead of just altering clip rects. This is in order to facilitate merging of draw commands. + // - After crossing FreezeRowsCount, all columns see their current draw channel increased. + // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. + // Draw channel allocation (before merging): + // - NoClip --> 1+1 channels: background + foreground (same clip rect == 1 draw call) + // - Clip --> 1+N channels + // - FreezeRows || FreezeColumns --> 1+N*2 (unless scrolling value is zero) + // - FreezeRows && FreezeColunns --> 2+N*2 (unless scrolling value is zero) + const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; + const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClipX) ? 1 : table->ColumnsActiveCount; + const int channels_for_background = 1; + const int channels_for_dummy = (table->ColumnsActiveCount < table->ColumnsCount) ? +1 : 0; + const int channels_total = channels_for_background + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; + table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total); + table->DummyDrawChannel = channels_for_dummy ? (ImS8)(channels_total - 1) : -1; + + int draw_channel_current = 1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->IsActive) + { + column->DrawChannelRowsBeforeFreeze = (ImS8)(draw_channel_current); + column->DrawChannelRowsAfterFreeze = (ImS8)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row : 0)); + if (!(table->Flags & ImGuiTableFlags_NoClipX)) + draw_channel_current++; + } + else + { + column->DrawChannelRowsBeforeFreeze = column->DrawChannelRowsAfterFreeze = table->DummyDrawChannel; + } + column->DrawChannelCurrent = column->DrawChannelRowsBeforeFreeze; + } +} + +// Adjust flags: default width mode + weighted columns are not allowed when auto extending +static ImGuiTableColumnFlags TableFixColumnFlags(ImGuiTable* table, ImGuiTableColumnFlags flags) +{ + // Sizing Policy + if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) + { + if (table->Flags & ImGuiTableFlags_SizingPolicyFixedX) + flags |= ((table->Flags & ImGuiTableFlags_Resizable) && !(flags & ImGuiTableColumnFlags_NoResize)) ? ImGuiTableColumnFlags_WidthFixed : ImGuiTableColumnFlags_WidthAlwaysAutoResize; + else + flags |= ImGuiTableColumnFlags_WidthStretch; + } + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. + if ((flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize))// || ((flags & ImGuiTableColumnFlags_WidthStretch) && (table->Flags & ImGuiTableFlags_SizingPolicyStretchX))) + flags |= ImGuiTableColumnFlags_NoResize; + //if ((flags & ImGuiTableColumnFlags_WidthStretch) && (table->Flags & ImGuiTableFlags_SizingPolicyFixedX)) + // flags = (flags & ~ImGuiTableColumnFlags_WidthMask_) | ImGuiTableColumnFlags_WidthFixed; + + // Sorting + if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending)) + flags |= ImGuiTableColumnFlags_NoSort; + + // Alignment + //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0) + // flags |= ImGuiTableColumnFlags_AlignCenter; + //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used. + + return flags; +} + +static void TableFixColumnSortDirection(ImGuiTableColumn* column) +{ + // Handle NoSortAscending/NoSortDescending + if (column->SortDirection == ImGuiSortDirection_Ascending && (column->Flags & ImGuiTableColumnFlags_NoSortAscending)) + column->SortDirection = ImGuiSortDirection_Descending; + else if (column->SortDirection == ImGuiSortDirection_Descending && (column->Flags & ImGuiTableColumnFlags_NoSortDescending)) + column->SortDirection = ImGuiSortDirection_Ascending; +} + +static float TableGetMinColumnWidth() +{ + ImGuiContext& g = *GImGui; + // return g.Style.ColumnsMinSpacing; + return g.Style.FramePadding.x * 3.0f; +} + +// Layout columns for the frame +// Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. +// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAuto columns, +// increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution for WidthAuto columns? +void ImGui::TableUpdateLayout(ImGuiTable* table) +{ + IM_ASSERT(table->IsLayoutLocked == false); + + // Compute offset, clip rect for the frame + const ImRect work_rect = table->WorkRect; + const float padding_auto_x = table->CellPaddingX1; // Can't make auto padding larger than what WorkRect knows about so right-alignment matches. + const float min_column_width = TableGetMinColumnWidth(); + + int count_fixed = 0; + float width_fixed = 0.0f; + float total_weights = 0.0f; + table->LeftMostStretchedColumnDisplayOrder = -1; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + const int column_n = table->DisplayOrder[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Adjust flags: default width mode + weighted columns are not allowed when auto extending + column->Flags = TableFixColumnFlags(table, column->FlagsIn); + + // We have a unusual edge case where if the user doesn't call TableGetSortSpecs() but has sorting enabled + // or varying sorting flags, we still want the sorting arrows to honor those flags. + if (table->Flags & ImGuiTableFlags_Sortable) + TableFixColumnSortDirection(column); + + if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) + { + // Latch initial size for fixed columns + count_fixed += 1; + const bool init_size = (column->AutoFitFrames > 0) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); + if (init_size) + { + // Combine width from regular rows + width from headers unless requested not to + float width_request = (float)ImMax(column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen); + if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) + width_request = ImMax(width_request, (float)column->ContentWidthHeadersDesired); + column->WidthRequested = ImMax(width_request + padding_auto_x, min_column_width); + + // FIXME-TABLE: Increase minimum size during init frame so avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. + // Otherwise what tends to happen is that TextWrapped would output a very large height (= first frame scrollbar display very off + clipper would skip lots of items) + // This is merely making the side-effect less extreme, but doesn't properly fixes it. + if (column->AutoFitFrames > 1 && table->IsFirstFrame) + column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); + } + width_fixed += column->WidthRequested; + } + else + { + IM_ASSERT(column->Flags & ImGuiTableColumnFlags_WidthStretch); + IM_ASSERT(column->ResizeWeight > 0.0f); + total_weights += column->ResizeWeight; + if (table->LeftMostStretchedColumnDisplayOrder == -1) + table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->IndexDisplayOrder; + } + + // Don't increment auto-fit until container window got a chance to submit its items + if (column->AutoFitFrames > 0 && table->BackupSkipItems == false) + column->AutoFitFrames--; + } + + // Layout + const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1); + float width_avail; + if ((table->Flags & ImGuiTableFlags_ScrollX) && (table->InnerWidth == 0.0f)) + width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; + else + width_avail = work_rect.GetWidth() - width_spacings - 1.0f; // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible + const float width_avail_for_stretched_columns = width_avail - width_fixed; + float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; + + // Apply final width based on requested widths + // Mark some columns as not resizable + int count_resizable = 0; + table->ColumnsTotalWidth = width_spacings; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrder[order_n]]; + + // Allocate width for stretched/weighted columns + if (column->Flags & ImGuiTableColumnFlags_WidthStretch) + { + float weight_ratio = column->ResizeWeight / total_weights; + column->WidthRequested = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); + width_remaining_for_stretched_columns -= column->WidthRequested; + + // [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing to left-side of fixed column + // We also need to copy the NoResize flag.. + if (column->NextActiveColumn != -1) + if (ImGuiTableColumn* next_column = &table->Columns[column->NextActiveColumn]) + if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed) + column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_); + } + + // [Resize Rule 1] The right-most active column is not resizable if there is at least one Stretch column (see comments in TableResizeColumn().) + if (column->NextActiveColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1) + column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; + + if (!(column->Flags & ImGuiTableColumnFlags_NoResize)) + count_resizable++; + + // Assign final width, record width in case we will need to shrink + column->WidthGiven = ImFloor(ImMax(column->WidthRequested, min_column_width)); + table->ColumnsTotalWidth += column->WidthGiven; + } + +#if 0 + const float width_excess = table->ColumnsTotalWidth - work_rect.GetWidth(); + if ((table->Flags & ImGuiTableFlags_SizingPolicyStretchX) && width_excess > 0.0f) + { + // Shrink widths when the total does not fit + // FIXME-TABLE: This is working but confuses/conflicts with manual resizing. + // FIXME-TABLE: Policy to shrink down below below ideal/requested width if there's no room? + g.ShrinkWidthBuffer.resize(table->ColumnsActiveCount); + for (int order_n = 0, active_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + const int column_n = table->DisplayOrder[order_n]; + g.ShrinkWidthBuffer[active_n].Index = column_n; + g.ShrinkWidthBuffer[active_n].Width = table->Columns[column_n].WidthGiven; + active_n++; + } + ShrinkWidths(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Size, width_excess); + for (int n = 0; n < g.ShrinkWidthBuffer.Size; n++) + table->Columns[g.ShrinkWidthBuffer.Data[n].Index].WidthGiven = ImMax(g.ShrinkWidthBuffer.Data[n].Width, min_column_size); + // FIXME: Need to alter table->ColumnsTotalWidth + } + else +#endif + + // Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column) + // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending where the mouse cursor is and/or relative weights. + // FIXME-TABLE: May be simpler to store floating width and floor final positions only + // FIXME-TABLE: Make it optional? User might prefer to preserve pixel perfect same size? + if (width_remaining_for_stretched_columns >= 1.0f) + for (int order_n = table->ColumnsCount - 1; total_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrder[order_n]]; + if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->WidthRequested += 1.0f; + column->WidthGiven += 1.0f; + width_remaining_for_stretched_columns -= 1.0f; + } + + // Setup final position, offset and clipping rectangles + int active_n = 0; + float offset_x = (table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x; + ImRect host_clip_rect = table->InnerClipRect; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + const int column_n = table->DisplayOrder[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == active_n) + offset_x += work_rect.Min.x - table->OuterRect.Min.x; + + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + { + // Hidden column: clear a few fields and we are done with it for the remainder of the function. + // We set a zero-width clip rect however we pay attention to set Min.y/Max.y properly to not interfere with the clipper. + column->MinX = column->MaxX = offset_x; + column->StartXRows = column->StartXHeaders = offset_x; + column->WidthGiven = 0.0f; + column->ClipRect.Min.x = offset_x; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.x = offset_x; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + continue; + } + + // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. + // Because of this we also know that all of the columns will always fit in table->WorkRect and therefore in table->InnerRect (because ScrollX is off) + if (!(table->Flags & ImGuiTableFlags_ScrollX)) + { + float max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; + if (offset_x + column->WidthGiven > max_x) + column->WidthGiven = ImMax(max_x - offset_x, min_column_width); + } + + column->MinX = offset_x; + column->MaxX = column->MinX + column->WidthGiven; + + const float initial_max_pos_x = column->MinX + table->CellPaddingX1; + column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; + column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; + + // Starting cursor position + column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; + + // Alignment + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. + // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) + //if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]); + //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) + // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]), 0.5f); + + //// A one pixel padding on the right side makes clipping more noticeable and contents look less cramped. + column->ClipRect.Min.x = column->MinX; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.x = column->MaxX;// -1.0f; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + + if (active_n < table->FreezeColumnsCount) + host_clip_rect.Min.x = ImMax(host_clip_rect.Min.x, column->MaxX + 2.0f); + + offset_x += column->WidthGiven + table->CellSpacingX; + active_n++; + } + + // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either because of using _WidthAlwaysAutoResize/_WidthStretch) + // This will hide the resizing option from the context menu. + if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) + table->Flags &= ~ImGuiTableFlags_Resizable; + + // Borders + if (table->Flags & ImGuiTableFlags_Resizable) + TableUpdateBorders(table); + + // Reset fields after we used them in TableSetupResize() + table->LastFirstRowHeight = 0.0f; + table->IsLayoutLocked = true; + table->IsUsingHeaders = false; + + // Context menu + if (table->IsContextPopupOpen) + { + if (BeginPopup("##TableContextMenu")) + { + TableDrawContextMenu(table, table->ContextPopupColumn); + EndPopup(); + } + else + { + table->IsContextPopupOpen = false; + } + } +} + +// Process interaction on resizing borders. Actual size change will be applied in EndTable() +// - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise +// - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets overlapping the same area. +void ImGui::TableUpdateBorders(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); + + // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and use + // the final height from last frame. Because this is only affecting _interaction_ with columns, it is not really problematic. + // (whereas the actual visual will be displayed in EndTable() and using the current frame height) + // Actual columns highlight/render will be performed in EndTable() and not be affected. + const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersFullHeight); + const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; + const float hit_y1 = table->OuterRect.Min.y; + const float hit_y2_full = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); + const float hit_y2 = borders_full_height ? hit_y2_full : (hit_y1 + table->LastFirstRowHeight); + const float mouse_x_hover_body = (g.IO.MousePos.y >= hit_y1 && g.IO.MousePos.y < hit_y2_full) ? g.IO.MousePos.x : FLT_MAX; + + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + + const int column_n = table->DisplayOrder[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Detect hovered column: + // - we perform an unusually low-level check here.. not using IsMouseHoveringRect() to avoid touch padding. + // - we don't care about the full set of IsItemHovered() feature either. + if (mouse_x_hover_body >= column->MinX && mouse_x_hover_body < column->MaxX) + table->HoveredColumnBody = (ImS8)column_n; + + if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) + continue; + + ImGuiID column_id = table->ID + (ImGuiID)column_n; + ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, hit_y2); + //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); + KeepAliveID(column_id); + + bool hovered = false, held = false; + ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (held) + table->ResizedColumn = (ImS8)column_n; + if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) + { + table->HoveredColumnBorder = (ImS8)column_n; + SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + } +} + +void ImGui::EndTable() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL); + + const ImGuiTableFlags flags = table->Flags; + ImGuiWindow* inner_window = table->InnerWindow; + ImGuiWindow* outer_window = table->OuterWindow; + IM_ASSERT(inner_window == g.CurrentWindow); + IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); + + if (table->IsInsideRow) + TableEndRow(table); + + // Finalize table height + inner_window->SkipItems = table->BackupSkipItems; + inner_window->DC.CursorMaxPos = table->BackupCursorMaxPos; + if (inner_window != outer_window) + { + table->OuterRect.Max.y = ImMax(table->OuterRect.Max.y, inner_window->Pos.y + inner_window->Size.y); + inner_window->DC.CursorMaxPos.y = table->RowPosY2; + } + else if (!(flags & ImGuiTableFlags_NoHostExtendY)) + { + table->OuterRect.Max.y = ImMax(table->OuterRect.Max.y, inner_window->DC.CursorPos.y); + inner_window->DC.CursorMaxPos.y = table->RowPosY2; + } + table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); + table->LastOuterHeight = table->OuterRect.GetHeight(); + + // Store content width reference for each column + float max_pos_x = inner_window->DC.CursorMaxPos.x; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Store content width (for both Headers and Rows) + //float ref_x = column->MinX; + float ref_x_rows = column->StartXRows - table->CellPaddingX1; + float ref_x_headers = column->StartXHeaders - table->CellPaddingX1; + column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); + column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); + column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); + column->ContentWidthHeadersDesired = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersDesired - ref_x_headers); + + if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) + max_pos_x = ImMax(max_pos_x, column->MaxX); + } + + // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. + inner_window->DC.CursorMaxPos.x = max_pos_x + 1; + + if (!(flags & ImGuiTableFlags_NoClipX)) + inner_window->DrawList->PopClipRect(); + inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); + + // Draw borders + if ((flags & ImGuiTableFlags_Borders) != 0) + TableDrawBorders(table); + + // Flatten channels and merge draw calls + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); + TableDrawMergeChannels(table); + + // When releasing a column being resized, scroll to keep the resulting column in sight + const float min_column_width = TableGetMinColumnWidth(); + if (!(table->Flags & ImGuiTableFlags_ScrollX) && inner_window != outer_window) + { + inner_window->Scroll.x = 0.0f; + } + else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX) + { + ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn]; + if (column->MaxX < table->InnerClipRect.Min.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - min_column_width, 1.0f); + else if (column->MaxX > table->InnerClipRect.Max.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + min_column_width, 1.0f); + } + + // Apply resizing/dragging at the end of the frame + // FIXME-TABLE: Preserve contents width _while resizing down_ until releasing. + // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling. + if (table->ResizedColumn != -1) + { + ImGuiTableColumn* column = &table->Columns[table->ResizedColumn]; + const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS); + const float new_width = ImFloor(new_x2 - column->MinX); + TableSetColumnWidth(table, column, new_width); + } + + // Layout in outer window + inner_window->WorkRect = table->BackupWorkRect; + inner_window->SkipItems = table->BackupSkipItems; + outer_window->DC.CursorPos = table->OuterRect.Min; + outer_window->DC.ColumnsOffset.x = 0.0f; + if (inner_window != outer_window) + { + // Override EndChild's ItemSize with our own to enable auto-resize on the X axis when possible + float backup_outer_cursor_pos_x = outer_window->DC.CursorPos.x; + EndChild(); + outer_window->DC.CursorMaxPos.x = backup_outer_cursor_pos_x + table->ColumnsTotalWidth + 1.0f + inner_window->ScrollbarSizes.x; + } + else + { + PopID(); + ImVec2 item_size = table->OuterRect.GetSize(); + item_size.x = table->ColumnsTotalWidth; + ItemSize(item_size); + } + + // Save settings + if (table->IsSettingsDirty) + TableSaveSettings(table); + + // Clear or restore current table, if any + IM_ASSERT(g.CurrentWindow == outer_window); + IM_ASSERT(g.CurrentTable == table); + outer_window->DC.CurrentTable = NULL; + g.CurrentTableStack.pop_back(); + g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; +} + +void ImGui::TableDrawBorders(ImGuiTable* table) +{ + ImGuiWindow* inner_window = table->InnerWindow; + ImGuiWindow* outer_window = table->OuterWindow; + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); + if (inner_window->Hidden || !table->HostClipRect.Overlaps(table->InnerClipRect)) + return; + + // Draw inner border and resizing feedback + const float draw_y1 = table->OuterRect.Min.y; + float draw_y2_base = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight; + float draw_y2_full = table->OuterRect.Max.y; + ImU32 border_base_col; + if (!table->IsUsingHeaders || (table->Flags & ImGuiTableFlags_BordersFullHeight)) + { + draw_y2_base = draw_y2_full; + border_base_col = table->BorderInnerColor; + } + else + { + border_base_col = table->BorderOuterColor; + } + + if (table->Flags & ImGuiTableFlags_BordersV) + { + const bool draw_left_most_border = (table->Flags & ImGuiTableFlags_BordersOuter) == 0; + if (draw_left_most_border) + inner_window->DrawList->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, 1.0f); + + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + + const int column_n = table->DisplayOrder[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + const bool is_hovered = (table->HoveredColumnBorder == column_n); + const bool is_resized = (table->ResizedColumn == column_n); + const bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); + if (draw_right_border && column->MaxX > column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. + { + // Draw in outer window so right-most column won't be clipped + // Always draw full height border when: + // - not using headers + // - user specify ImGuiTableFlags_BordersFullHeight + // - being interacted with + // - on the delimitation of frozen column scrolling + const ImU32 col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : border_base_col; + float draw_y2 = draw_y2_base; + if (is_hovered || is_resized || (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1)) + draw_y2 = draw_y2_full; + inner_window->DrawList->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, 1.0f); + } + } + } + + // Draw outer border + if (table->Flags & ImGuiTableFlags_BordersOuter) + { + // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call + // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their parent. + // In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part of it in inner window, + // and the part that's over scrollbars in the outer window..) + // Either solution currently won't allow us to use a larger border size: the border would clipped. + ImRect outer_border = table->OuterRect; + if (inner_window != outer_window) + outer_border.Expand(1.0f); + outer_window->DrawList->AddRect(outer_border.Min, outer_border.Max, table->BorderOuterColor); // IM_COL32(255, 0, 0, 255)); + } + else if (table->Flags & ImGuiTableFlags_BordersH) + { + // Draw bottom-most border + const float border_y = table->RowPosY2; + if (border_y >= table->BackgroundClipRect.Min.y && border_y < table->BackgroundClipRect.Max.y) + inner_window->DrawList->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderOuterColor); + } +} + +static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) +{ + IM_ASSERT(table->LeftMostStretchedColumnDisplayOrder != -1); + + // Measure existing quantity + float visible_weight = 0.0f; + float visible_width = 0.0f; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsActive || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + visible_weight += column->ResizeWeight; + visible_width += column->WidthRequested; + } + IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f); + + // Apply new weights + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsActive || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->ResizeWeight = (column->WidthRequested + 0.0f) / visible_width; + } +} + +void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, float column_0_width) +{ + // Constraints + float min_width = TableGetMinColumnWidth(); + float max_width_0 = FLT_MAX; + if (!(table->Flags & ImGuiTableFlags_ScrollX)) + max_width_0 = (table->WorkRect.Max.x - column_0->MinX) - (table->ColumnsActiveCount - (column_0->IndexWithinActiveSet + 1)) * min_width; + column_0_width = ImClamp(column_0_width, min_width, max_width_0); + + // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) + if (column_0->WidthGiven == column_0_width || column_0->WidthRequested == column_0_width) + return; + + ImGuiTableColumn* column_1 = (column_0->NextActiveColumn != -1) ? &table->Columns[column_0->NextActiveColumn] : NULL; + + // In this surprisingly not simple because of how we support mixing Fixed and Stretch columns. + // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1. + // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user. + // Scenarios: + // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset. + // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered. + // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Weighted column will always be minimal size. + // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 W3 resize from W1| or W2| --> FIXME + // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 F3 resize from F3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 resize from F2| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 F3 resize from W1| or W2| --> ok + // - W1 F2 W3 resize from W1| or F2| --> FIXME + // - F1 W2 F3 resize from W2| --> ok + // - W1 F2 F3 resize from W1| --> ok: equivalent to resizing |F2. F3 will not move. (forwarded by Resize Rule 2) + // - W1 F2 F3 resize from F2| --> FIXME should resize F2, F3 and not have effect on W1 (Stretch columns are _before_ the Fixed column). + + // Rules: + // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableSetupLayout(). + // - [Resize Rule 2] Resizing from right-side of a Stretch column before a fixed column froward sizing to left-side of fixed column. + // - [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure that our left border won't move. + + if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) + { + // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to + // ensure that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others. + if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) + if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->IndexDisplayOrder) + { + // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) + float column_1_width = ImMax(column_1->WidthRequested - (column_0_width - column_0->WidthRequested), min_width); + column_0_width = column_0->WidthRequested + column_1->WidthRequested - column_1_width; + column_1->WidthRequested = column_1_width; + } + + // Apply + //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthRequested, column_0_width); + column_0->WidthRequested = column_0_width; + } + else if (column_0->Flags & ImGuiTableColumnFlags_WidthStretch) + { + // [Resize Rule 2] + if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) + { + float off = (column_0->WidthGiven - column_0_width); + float column_1_width = column_1->WidthGiven + off; + column_1->WidthRequested = ImMax(min_width, column_1_width); + return; + } + + // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) + float column_1_width = ImMax(column_1->WidthRequested - (column_0_width - column_0->WidthRequested), min_width); + column_0_width = column_0->WidthRequested + column_1->WidthRequested - column_1_width; + column_1->WidthRequested = column_1_width; + column_0->WidthRequested = column_0_width; + TableUpdateColumnsWeightFromWidth(table); + } + table->IsSettingsDirty = true; +} + +// Columns where the contents didn't stray off their local clip rectangle can be merged into a same draw command. +// To achieve this we merge their clip rect and make them contiguous in the channel list so they can be merged. +// So here we'll reorder the draw cmd which can be merged, by arranging them into a maximum of 4 distinct groups: +// +// 1 group: 2 groups: 2 groups: 4 groups: +// [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze +// [ .. ] or no scroll [ 1. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll +// +// Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). +// When the contents of a column didn't stray off its limit, we move its channels into the corresponding group +// based on its position (within frozen rows/columns set or not). +// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be merged by the DrawSplitter.Merge() call. +// +// Column channels will not be merged into one of the 1-4 groups in the following cases: +// - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). +// Direct ImDrawList calls won't be noticed so if you use them make sure the ImGui:: bounds matches, by e.g. calling SetCursorScreenPos(). +// - The channel uses more than one draw command itself (we drop all our merging stuff here.. we could do better but it's going to be rare) +// +// This function is particularly tricky to understand.. take a breath. +void ImGui::TableDrawMergeChannels(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImDrawListSplitter* splitter = &table->DrawSplitter; + const bool is_frozen_v = (table->FreezeRowsCount > 0); + const bool is_frozen_h = (table->FreezeColumnsCount > 0); + + int merge_set_mask = 0; + int merge_set_channels_count[4] = { 0 }; + ImU64 merge_set_channels_mask[4] = { 0 }; + ImRect merge_set_clip_rect[4]; + for (int n = 0; n < IM_ARRAYSIZE(merge_set_clip_rect); n++) + merge_set_clip_rect[n] = ImVec4(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + bool merge_set_all_fit_within_inner_rect = (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0; + + // 1. Scan channels and take note of those who can be merged + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + { + if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + continue; + const int column_n = table->DisplayOrder[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + + const int merge_set_sub_count = is_frozen_v ? 2 : 1; + for (int merge_set_sub_n = 0; merge_set_sub_n < merge_set_sub_count; merge_set_sub_n++) + { + const int channel_no = (merge_set_sub_n == 0) ? column->DrawChannelRowsBeforeFreeze : column->DrawChannelRowsAfterFreeze; + + // Don't attempt to merge if there are multiple calls within the column + ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; + if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0) + src_channel->_CmdBuffer.pop_back(); + if (src_channel->_CmdBuffer.Size != 1) + continue; + + // Find out the width of this merge set and check if it will fit in our column. + float width_contents; + if (merge_set_sub_count == 1) // No row freeze (same as testing !is_frozen_v) + width_contents = ImMax(column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed); + else if (merge_set_sub_n == 0) // Row freeze: use width before freeze + width_contents = ImMax(column->ContentWidthRowsFrozen, column->ContentWidthHeadersUsed); + else // Row freeze: use width after freeze + width_contents = column->ContentWidthRowsUnfrozen; + if (width_contents > column->WidthGiven && !(column->Flags & ImGuiTableColumnFlags_NoClipX)) + continue; + + const int dst_merge_set_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_set_sub_n : 1); + IM_ASSERT(merge_set_channels_count[dst_merge_set_n] < (int)sizeof(merge_set_channels_mask[dst_merge_set_n]) * 8); + merge_set_mask |= (1 << dst_merge_set_n); + merge_set_channels_mask[dst_merge_set_n] |= (ImU64)1 << channel_no; + merge_set_channels_count[dst_merge_set_n]++; + merge_set_clip_rect[dst_merge_set_n].Add(src_channel->_CmdBuffer[0].ClipRect); + + // If we end with a single set and hosted by the outer window, we'll attempt to merge our draw command with + // the existing outer window command. But we can only do so if our columns all fit within the expected clip rect, + // otherwise clipping will be incorrect when ScrollX is disabled. + // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column don't fit within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect + + // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to fix... + // cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." + // 2019/10/22: (2) Clamping code in TableSetupLayout() seemingly made this not necessary... +#if 0 + if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) + merge_set_all_fit_within_inner_rect = false; +#endif + } + + // Invalidate current draw channel (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging) + column->DrawChannelCurrent = -1; + } + + // 2. Rewrite channel list in our preferred order + if (merge_set_mask != 0) + { + // Use shared temporary storage so the allocation gets amortized + g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - 1); + ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; + ImU64 remaining_mask = ((splitter->_Count < 64) ? ((ImU64)1 << splitter->_Count) - 1 : ~(ImU64)0) & ~1; + const bool may_extend_clip_rect_to_host_rect = ImIsPowerOfTwo(merge_set_mask); + for (int merge_set_n = 0; merge_set_n < 4; merge_set_n++) + if (merge_set_channels_count[merge_set_n]) + { + ImU64 merge_channels_mask = merge_set_channels_mask[merge_set_n]; + ImRect merge_clip_rect = merge_set_clip_rect[merge_set_n]; + if (may_extend_clip_rect_to_host_rect) + { + //GetOverlayDrawList()->AddRect(table->HostClipRect.Min, table->HostClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 3.0f); + //GetOverlayDrawList()->AddRect(table->InnerClipRect.Min, table->InnerClipRect.Max, IM_COL32(0, 255, 0, 200), 0.0f, ~0, 1.0f); + //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 2.0f); + merge_clip_rect.Add(merge_set_all_fit_within_inner_rect ? table->HostClipRect : table->InnerClipRect); + //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(0, 255, 0, 200)); + } + remaining_mask &= ~merge_channels_mask; + for (int n = 0; n < splitter->_Count && merge_channels_mask != 0; n++) + { + // Copy + overwrite new clip rect + const ImU64 n_mask = (ImU64)1 << n; + if ((merge_channels_mask & n_mask) == 0) + continue; + ImDrawChannel* channel = &splitter->_Channels[n]; + IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); + channel->_CmdBuffer[0].ClipRect = *(ImVec4*)&merge_clip_rect; + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + merge_channels_mask &= ~n_mask; + } + } + + // Append channels that we didn't reorder at the end of the list + for (int n = 0; n < splitter->_Count && remaining_mask != 0; n++) + { + const ImU64 n_mask = (ImU64)1 << n; + if ((remaining_mask & n_mask) == 0) + continue; + ImDrawChannel* channel = &splitter->_Channels[n]; + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + remaining_mask &= ~n_mask; + } + IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); + memcpy(splitter->_Channels.Data + 1, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - 1) * sizeof(ImDrawChannel)); + } + + // 3. Actually merge (channels using the same clip rect will be contiguous and naturally merged) + splitter->Merge(table->InnerWindow->DrawList); +} + +// We use a default parameter of 'init_width_or_weight == -1' +// ImGuiTableColumnFlags_WidthFixed, width <= 0 --> init width == auto +// ImGuiTableColumnFlags_WidthFixed, width > 0 --> init width == manual +// ImGuiTableColumnFlags_WidthStretch, weight < 0 --> init weight == 1.0f +// ImGuiTableColumnFlags_WidthStretch, weight >= 0 --> init weight == custom +// Use a different API? +void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Can only call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(!table->IsLayoutLocked && "Can only call TableSetupColumn() before first row!"); + IM_ASSERT(table->DeclColumnsCount >= 0 && table->DeclColumnsCount < table->ColumnsCount && "Called TableSetupColumn() too many times!"); + + ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; + table->DeclColumnsCount++; + + column->UserID = user_id; + column->FlagsIn = flags; + column->Flags = TableFixColumnFlags(table, column->FlagsIn); + flags = column->Flags; + + // Initialize defaults + if (table->IsFirstFrame && !table->IsSettingsLoaded) + { + // Init width or weight + // Disable auto-fit if a default fixed width has been specified + if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) + { + column->WidthRequested = init_width_or_weight; + column->AutoFitFrames = 0; + } + if (flags & ImGuiTableColumnFlags_WidthStretch) + { + IM_ASSERT(init_width_or_weight < 0.0f || init_width_or_weight > 0.0f); + column->ResizeWeight = (init_width_or_weight < 0.0f ? 1.0f : init_width_or_weight); + } + else + { + column->ResizeWeight = 1.0f; + } + + // Init default visibility/sort state + if (flags & ImGuiTableColumnFlags_DefaultHide) + column->IsActive = column->NextIsActive = false; + if (flags & ImGuiTableColumnFlags_DefaultSort) + column->SortOrder = 0; // Multiple columns using _DefaultSort will be reordered when building the sort specs. + } + + // Store name (append with zero-terminator in contiguous buffer) + IM_ASSERT(column->NameOffset == -1); + if (label != NULL) + { + column->NameOffset = (ImS16)table->ColumnsNames.size(); + table->ColumnsNames.append(label, label + strlen(label) + 1); + } +} + +// Starts into the first cell of a new row +void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float min_row_height) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + if (table->CurrentRow == -1) + TableUpdateLayout(table); + else if (table->IsInsideRow) + TableEndRow(table); + + table->LastRowFlags = table->RowFlags; + table->RowFlags = row_flags; + TableBeginRow(table); + + // We honor min_height requested by user, but cannot guarantee per-row maximum height as that would essentially require a unique clipping rectangle per-cell. + table->RowPosY2 += min_row_height; + + TableBeginCell(table, 0); +} + +// [Internal] +void ImGui::TableBeginRow(ImGuiTable* table) +{ + ImGuiWindow* window = table->InnerWindow; + IM_ASSERT(!table->IsInsideRow); + + // New row + table->CurrentRow++; + table->CurrentColumn = -1; + table->RowBgColor = IM_COL32_DISABLE; + table->IsInsideRow = true; + + // Begin frozen rows + float next_y1 = table->RowPosY2; + if (table->CurrentRow == 0 && table->FreezeRowsCount > 0) + next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y; + + table->RowPosY1 = table->RowPosY2 = next_y1; + table->RowTextBaseline = 0.0f; + window->DC.CursorMaxPos.y = next_y1; + + // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. + if (table->RowFlags & ImGuiTableRowFlags_Headers) + { + table->RowBgColor = GetColorU32(ImGuiCol_TableHeaderBg); + if (table->CurrentRow == 0) + table->IsUsingHeaders = true; + } +} + +// [Internal] +void ImGui::TableEndRow(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window == table->InnerWindow); + IM_ASSERT(table->IsInsideRow); + + TableEndCell(table); + + table->RowPosY2 += table->CellPaddingY; + + // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. + // However it is likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. + window->DC.CursorPos.y = table->RowPosY2; + + // Row background fill + const float bg_y1 = table->RowPosY1; + const float bg_y2 = table->RowPosY2; + + if (table->CurrentRow == 0) + table->LastFirstRowHeight = bg_y2 - bg_y1; + + if (table->CurrentRow >= 0 && bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y) + { + // Decide of background color for the row + ImU32 bg_col = 0; + if (table->RowBgColor != IM_COL32_DISABLE) + bg_col = table->RowBgColor; + else if (table->Flags & ImGuiTableFlags_RowBg) + bg_col = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + + // Decide of separating border color + ImU32 border_col = 0; + if (table->CurrentRow != 0 || table->InnerWindow == table->OuterWindow) + { + if (table->Flags & ImGuiTableFlags_BordersH) + { + if (table->CurrentRow == 0 && table->InnerWindow == table->OuterWindow) + border_col = table->BorderOuterColor; + else if (!(table->LastRowFlags & ImGuiTableRowFlags_Headers)) + border_col = table->BorderInnerColor; + } + else + { + if (table->RowFlags & ImGuiTableRowFlags_Headers) + border_col = table->BorderOuterColor; + } + } + + if (bg_col != 0 || border_col != 0) + table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); + + // Draw background + // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle + if (bg_col) + { + ImRect bg_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); + bg_rect.ClipWith(table->BackgroundClipRect); + if (bg_rect.Min.y < bg_rect.Max.y) + window->DrawList->AddRectFilledMultiColor(bg_rect.Min, bg_rect.Max, bg_col, bg_col, bg_col, bg_col); + } + + // Draw top border + const float border_y = bg_y1; + if (border_col && border_y >= table->BackgroundClipRect.Min.y && border_y < table->BackgroundClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), border_col); + } + + const bool unfreeze_rows = (table->CurrentRow + 1 == table->FreezeRowsCount && table->FreezeRowsCount > 0); + + // Draw bottom border (always strong) + const bool draw_separating_border = unfreeze_rows || (table->RowFlags & ImGuiTableRowFlags_Headers); + if (draw_separating_border) + if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderOuterColor); + + // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) + // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and get the new cursor position. + if (unfreeze_rows) + { + IM_ASSERT(table->IsFreezeRowsPassed == false); + table->IsFreezeRowsPassed = true; + table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); + + ImRect r; + r.Min.x = table->InnerClipRect.Min.x; + r.Min.y = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); + r.Max.x = table->InnerClipRect.Max.x; + r.Max.y = window->InnerClipRect.Max.y; + table->BackgroundClipRect = r; + + float row_height = table->RowPosY2 - table->RowPosY1; + table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; + table->RowPosY1 = table->RowPosY2 - row_height; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->DrawChannelCurrent = column->DrawChannelRowsAfterFreeze; + column->ClipRect.Min.y = r.Min.y; + } + } + + if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) + table->RowBgColorCounter++; + table->IsInsideRow = false; +} + +// [Internal] This is called a lot, so we need to be mindful of unnecessary overhead! +void ImGui::TableBeginCell(ImGuiTable* table, int column_no) +{ + table->CurrentColumn = column_no; + ImGuiTableColumn* column = &table->Columns[column_no]; + ImGuiWindow* window = table->InnerWindow; + + const float start_x = (table->RowFlags & ImGuiTableRowFlags_Headers) ? column->StartXHeaders : column->StartXRows; + + window->DC.LastItemId = 0; + window->DC.CursorPos = ImVec2(start_x, table->RowPosY1 + table->CellPaddingY); + window->DC.CursorMaxPos.x = window->DC.CursorPos.x; + window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT // FIXME-TABLE: Recurse + window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; + + window->WorkRect.Min.y = window->DC.CursorPos.y; + window->WorkRect.Min.x = column->MinX + table->CellPaddingX1; + window->WorkRect.Max.x = column->MaxX - table->CellPaddingX2; + + // To allow ImGuiListClipper to function we propagate our row height + if (!column->IsActive) + window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); + + // FIXME-COLUMNS: Setup baseline, preserve across columns (how can we obtain first line baseline tho..) + // window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); + + window->SkipItems = column->IsActive ? table->BackupSkipItems : true; + if (table->Flags & ImGuiTableFlags_NoClipX) + { + table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); + } + else + { + table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + //window->ClipRect = column->ClipRect; + //IM_ASSERT(column->ClipRect.Max.x > column->ClipRect.Min.x && column->ClipRect.Max.y > column->ClipRect.Min.y); + //window->DrawList->_ClipRectStack.back() = ImVec4(column->ClipRect.Min.x, column->ClipRect.Min.y, column->ClipRect.Max.x, column->ClipRect.Max.y); + //window->DrawList->UpdateClipRect(); + window->DrawList->PopClipRect(); + window->DrawList->PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); + //IMGUI_DEBUG_LOG("%d (%.0f,%.0f)(%.0f,%.0f)\n", column_no, column->ClipRect.Min.x, column->ClipRect.Min.y, column->ClipRect.Max.x, column->ClipRect.Max.y); + window->ClipRect = window->DrawList->_ClipRectStack.back(); + } +} + +// [Internal] +void ImGui::TableEndCell(ImGuiTable* table) +{ + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + ImGuiWindow* window = table->InnerWindow; + + // Report maximum position so we can infer content size per column. + float* p_max_pos_x; + if (table->RowFlags & ImGuiTableRowFlags_Headers) + p_max_pos_x = &column->ContentMaxPosHeadersUsed; // Useful in case user submit contents in header row that is not a TableHeader() call + else + p_max_pos_x = table->IsFreezeRowsPassed ? &column->ContentMaxPosRowsUnfrozen : &column->ContentMaxPosRowsFrozen; + *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x); + table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y); + + // Propagate text baseline for the entire row + // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. + table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset); +} + +// Append into the next cell +// FIXME-TABLE: Wrapping to next row should be optional? +bool ImGui::TableNextCell() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + if (table->CurrentColumn != -1 && table->CurrentColumn + 1 < table->ColumnsCount) + { + TableEndCell(table); + TableBeginCell(table, table->CurrentColumn + 1); + } + else + { + TableNextRow(); + } + + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + return column->IsActive; +} + +const char* ImGui::TableGetColumnName(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return NULL; + if (column_n < 0) + column_n = table->CurrentColumn; + return TableGetColumnName(table, column_n); +} + +bool ImGui::TableGetColumnIsVisible(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + if (column_n < 0) + column_n = table->CurrentColumn; + return (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +int ImGui::TableGetColumnIndex() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return 0; + return table->CurrentColumn; +} + +bool ImGui::TableSetColumnIndex(int column_idx) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + + if (table->CurrentColumn != column_idx) + { + if (table->CurrentColumn != -1) + TableEndCell(table); + IM_ASSERT(column_idx >= 0 && table->ColumnsCount); + TableBeginCell(table, column_idx); + } + + return (table->ActiveMaskByIndex & ((ImU64)1 << column_idx)) != 0; +} + +ImRect ImGui::TableGetCellRect() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + return ImRect(column->MinX, table->RowPosY1, column->MaxX, table->RowPosY2); +} + +const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_no) +{ + ImGuiTableColumn* column = &table->Columns[column_no]; + if (column->NameOffset == -1) + return NULL; + return &table->ColumnsNames.Buf[column->NameOffset]; +} + +void ImGui::PushTableBackground() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); + PushClipRect(table->HostClipRect.Min, table->HostClipRect.Max, false); +} + +void ImGui::PopTableBackground() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiTable* table = g.CurrentTable; + ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + PopClipRect(); +} + +// FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? +void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + bool want_separator = false; + selected_column_n = ImClamp(selected_column_n, -1, table->ColumnsCount - 1); + + // Sizing + if (table->Flags & ImGuiTableFlags_Resizable) + { + if (ImGuiTableColumn* selected_column = (selected_column_n != -1) ? &table->Columns[selected_column_n] : NULL) + { + const bool can_resize = !(selected_column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && selected_column->IsActive; + if (MenuItem("Size column to fit", NULL, false, can_resize)) + selected_column->AutoFitFrames = 1; + } + + if (MenuItem("Size all columns to fit", NULL)) + { + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->IsActive) + column->AutoFitFrames = 1; + } + } + want_separator = true; + } + + // Ordering + if (table->Flags & ImGuiTableFlags_Reorderable) + { + if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder)) + table->IsResetDisplayOrderRequest = true; + want_separator = true; + } + + // Hiding / Visibility + if (table->Flags & ImGuiTableFlags_Hideable) + { + if (want_separator) + Separator(); + want_separator = false; + + PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + const char* name = TableGetColumnName(table, column_n); + if (name == NULL) + name = ""; + + // Make sure we can't hide the last active column + bool menu_item_active = (column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; + if (column->IsActive && table->ColumnsActiveCount <= 1) + menu_item_active = false; + if (MenuItem(name, NULL, column->IsActive, menu_item_active)) + column->NextIsActive = !column->IsActive; + } + PopItemFlag(); + } +} + +// This is a helper to output headers based on the column names declared in TableSetupColumn() +// The intent is that advanced users would not need to use this helper and may create their own. +void ImGui::TableAutoHeaders() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table && table->CurrentRow == -1); + + int open_context_popup = INT_MAX; + + // This for loop is constructed to not make use of internal functions, + // as this is intended to be a base template to copy and build from. + TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight()); + const int columns_count = table->ColumnsCount; + for (int column_n = 0; column_n < columns_count; column_n++) + { + if (!TableSetColumnIndex(column_n)) + continue; + + const char* name = TableGetColumnName(column_n); + + // FIXME-TABLE: Test custom user elements +#if 0 + if (column_n < 2) + { + static bool b[10] = {}; + PushID(column_n); + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + Checkbox("##", &b[column_n]); + PopStyleVar(); + PopID(); + SameLine(0.0f, g.Style.ItemInnerSpacing.x); + } +#endif + + // [DEBUG] + //if (g.IO.KeyCtrl) { static char buf[32]; name = buf; ImGuiTableColumn* c = &table->Columns[column_n]; if (c->Flags & ImGuiTableColumnFlags_WidthStretch) ImFormatString(buf, 32, "%.3f>%.1f", c->ResizeWeight, c->WidthGiven); else ImFormatString(buf, 32, "%.1f", c->WidthGiven); } + + PushID(column_n); // Allow unnamed labels (generally accidental, but let's behave nicely with them) + TableHeader(name); + PopID(); + + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden + if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + open_context_popup = column_n; + } + + // FIXME-TABLE: This is not user-land code any more... + window->SkipItems = table->BackupSkipItems; + + // Allow opening popup from the right-most section after the last column + // FIXME-TABLE: This is not user-land code any more... perhaps instead we should expose hovered column. + // and allow some sort of row-centric IsItemHovered() for full flexibility? + const float unused_x1 = (table->RightMostActiveColumn != -1) ? table->Columns[table->RightMostActiveColumn].MaxX : table->WorkRect.Min.x; + if (unused_x1 < table->WorkRect.Max.x) + { + // FIXME: We inherit ClipRect/SkipItem from last submitted column (active or not), let's override + window->ClipRect = table->InnerClipRect; + + ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; + window->DC.CursorPos = ImVec2(unused_x1, table->RowPosY1); + ImVec2 size = ImVec2(table->WorkRect.Max.x - window->DC.CursorPos.x, table->RowPosY2 - table->RowPosY1); + if (size.x > 0.0f && size.y > 0.0f) + { + InvisibleButton("##RemainingSpace", size); + window->DC.CursorPos.y -= g.Style.ItemSpacing.y; + window->DC.CursorMaxPos = backup_cursor_max_pos; // Don't feed back into the width of the Header row + + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden + if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + open_context_popup = -1; + } + + window->ClipRect = window->DrawList->_ClipRectStack.back(); + } + + // Context Menu + if (open_context_popup != INT_MAX) + { + table->IsContextPopupOpen = true; + table->ContextPopupColumn = (ImS8)open_context_popup; + OpenPopup("##TableContextMenu"); + } +} + +// Emit a column header (text + optional sort order) +// We cpu-clip text here so that all columns headers can be merged into a same draw call. +// FIXME-TABLE: Should hold a selection state. +void ImGui::TableHeader(const char* label) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table->CurrentColumn != -1); + const int column_n = table->CurrentColumn; + ImGuiTableColumn* column = &table->Columns[column_n]; + + float row_height = GetTextLineHeight(); + ImRect cell_r = TableGetCellRect(); + ImRect work_r = cell_r; + work_r.Min.x = window->DC.CursorPos.x; + work_r.Max.y = work_r.Min.y + row_height; + + // Label + if (label == NULL) + label = ""; + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, true); + ImVec2 label_pos = window->DC.CursorPos; + float ellipsis_max = work_r.Max.x; + + // Selectable + PushID(label); + + // FIXME-TABLE: Fix when padding are disabled. + //window->DC.CursorPos.x = column->MinX + table->CellPadding.x; + + // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) + const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n); + const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, row_height)); + const bool held = IsItemActive(); + window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; + + // Drag and drop: re-order columns. Frozen columns are not reorderable. + // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. + if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) + { + // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x + table->ReorderColumn = (ImS8)column_n; + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) + if (column->PrevActiveColumn != -1 && (column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (table->Columns[column->PrevActiveColumn].IndexWithinActiveSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = -1; + if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) + if (column->NextActiveColumn != -1 && (column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (table->Columns[column->NextActiveColumn].IndexWithinActiveSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = +1; + } + + // Sort order arrow + float w_arrow = 0.0f; + float w_sort_text = 0.0f; + if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) + { + const float ARROW_SCALE = 0.75f; + w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);// table->CellPadding.x); + if (column->SortOrder != -1) + { + w_sort_text = 0.0f; + + char sort_order_suf[8]; + if (column->SortOrder > 0) + { + ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1); + w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; + } + + float x = ImMax(cell_r.Min.x, work_r.Max.x - w_arrow - w_sort_text); + ellipsis_max -= w_arrow + w_sort_text; + + float y = label_pos.y; + ImU32 col = GetColorU32(ImGuiCol_Text); + if (column->SortOrder > 0) + { + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f)); + RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf); + PopStyleColor(); + x += w_sort_text; + } + RenderArrow(window->DrawList, ImVec2(x, y), col, column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Down : ImGuiDir_Up, ARROW_SCALE); + } + + // Handle clicking on column header to adjust Sort Order + if (pressed && table->ReorderColumn != column_n) + TableSortSpecsClickColumn(table, column, g.IO.KeyShift); + } + if (!held && table->ReorderColumn == column_n) + table->ReorderColumn = -1; + + // Render clipped label + // Clipping here ensure that in the majority of situations, all our header cells will be merged into a single draw call. + //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + row_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + + // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. + // FIXME-TABLE: Clarify policies of how label width and potential decorations (arrows) fit into auto-resize of the column + float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; + column->ContentMaxPosHeadersUsed = ImMax(column->ContentMaxPosHeadersUsed, work_r.Max.x);// ImMin(max_pos_x, work_r.Max.x)); + column->ContentMaxPosHeadersDesired = ImMax(column->ContentMaxPosHeadersDesired, max_pos_x); + + PopID(); +} + +void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* clicked_column, bool add_to_existing_sort_orders) +{ + if (!(table->Flags & ImGuiTableFlags_MultiSortable)) + add_to_existing_sort_orders = false; + + ImS8 sort_order_max = 0; + if (add_to_existing_sort_orders) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + sort_order_max = ImMax(sort_order_max, table->Columns[column_n].SortOrder); + + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column == clicked_column) + { + // Set new sort direction and sort order + // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. + // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. + // - Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert + // the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. + if (column->SortOrder == -1) + column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); + else + column->SortDirection = (ImU8)((column->SortDirection == ImGuiSortDirection_Ascending) ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending); + if (column->SortOrder == -1 || !add_to_existing_sort_orders) + column->SortOrder = add_to_existing_sort_orders ? sort_order_max + 1 : 0; + } + else + { + if (!add_to_existing_sort_orders) + column->SortOrder = -1; + } + TableFixColumnSortDirection(column); + } + table->IsSettingsDirty = true; + table->IsSortSpecsDirty = true; +} + +// Return NULL if no sort specs. +// Return ->WantSort == true when the specs have changed since the last query. +const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL); + + if (!(table->Flags & ImGuiTableFlags_Sortable)) + return NULL; + + // Flatten sort specs into user facing data + const bool was_dirty = table->IsSortSpecsDirty; + if (was_dirty) + { + TableSortSpecsSanitize(table); + + // Write output + table->SortSpecsData.resize(table->SortSpecsCount); + table->SortSpecs.ColumnsMask = 0x00; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder == -1) + continue; + ImGuiTableSortSpecsColumn* sort_spec = &table->SortSpecsData[column->SortOrder]; + sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnIndex = (ImU8)column_n; + sort_spec->SortOrder = (ImU8)column->SortOrder; + sort_spec->SortSign = (column->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + sort_spec->SortDirection = column->SortDirection; + table->SortSpecs.ColumnsMask |= (ImU64)1 << column_n; + } + } + + // User facing data + table->SortSpecs.Specs = table->SortSpecsData.Data; + table->SortSpecs.SpecsCount = table->SortSpecsData.Size; + table->SortSpecs.SpecsChanged = was_dirty; + table->IsSortSpecsDirty = false; + return table->SortSpecs.SpecsCount ? &table->SortSpecs : NULL; +} + +bool ImGui::TableGetColumnIsSorted(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + if (column_n < 0) + column_n = table->CurrentColumn; + ImGuiTableColumn* column = &table->Columns[column_n]; + return (column->SortOrder != -1); +} + +void ImGui::TableSortSpecsSanitize(ImGuiTable* table) +{ + // Clear SortOrder from hidden column and verify that there's no gap or duplicate. + int sort_order_count = 0; + ImU64 sort_order_mask = 0x00; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder != -1 && !column->IsActive) + column->SortOrder = -1; + if (column->SortOrder == -1) + continue; + sort_order_count++; + sort_order_mask |= ((ImU64)1 << column->SortOrder); + IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8); + } + + const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1); + const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_MultiSortable); + if (need_fix_linearize || need_fix_single_sort_order) + { + ImU64 fixed_mask = 0x00; + for (int sort_n = 0; sort_n < sort_order_count; sort_n++) + { + // Fix: Rewrite sort order fields if needed so they have no gap or duplicate. + // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1) + int column_with_smallest_sort_order = -1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1) + if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder) + column_with_smallest_sort_order = column_n; + IM_ASSERT(column_with_smallest_sort_order != -1); + fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order); + table->Columns[column_with_smallest_sort_order].SortOrder = (ImS8)sort_n; + + // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. + if (need_fix_single_sort_order) + { + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (column_n != column_with_smallest_sort_order) + table->Columns[column_n].SortOrder = -1; + break; + } + } + } + + // Fallback default sort order (if no column has the ImGuiTableColumnFlags_DefaultSort flag) + if (sort_order_count == 0 && table->IsFirstFrame) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!(column->Flags & ImGuiTableColumnFlags_NoSort) && column->IsActive) + { + sort_order_count = 1; + column->SortOrder = 0; + break; + } + } + + table->SortSpecsCount = (ImS8)sort_order_count; +} + +//------------------------------------------------------------------------- +// TABLE - .ini settings +//------------------------------------------------------------------------- +// [Init] 1: TableSettingsHandler_ReadXXXX() Load and parse .ini file into TableSettings. +// [Main] 2: TableLoadSettings() When table is created, bind Table to TableSettings, serialize TableSettings data into Table. +// [Main] 3: TableSaveSettings() When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty. +// [Main] 4: TableSettingsHandler_WriteAll() When .ini file is dirty (which can come from other source), save TableSettings into .ini file. +//------------------------------------------------------------------------- + +static ImGuiTableSettings* CreateTableSettings(ImGuiID id, int columns_count) +{ + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings)); + IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); + ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings(); + for (int n = 0; n < columns_count; n++, settings_column++) + IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); + settings->ID = id; + settings->ColumnsCount = settings->ColumnsCountMax = (ImS8)columns_count; + return settings; +} + +static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id) +{ + // FIXME-OPT: Might want to store a lookup map for this? + ImGuiContext& g = *GImGui; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID == id) + return settings; + return NULL; +} + +ImGuiTableSettings* ImGui::TableFindSettings(ImGuiTable* table) +{ + if (table->SettingsOffset == -1) + return NULL; + + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + IM_ASSERT(settings->ID == table->ID); + if (settings->ColumnsCountMax < table->ColumnsCount) + { + settings->ID = 0; // Ditch storage if we won't fit because of a count change + return NULL; + } + return settings; +} + +void ImGui::TableSaveSettings(ImGuiTable* table) +{ + table->IsSettingsDirty = false; + if (table->Flags & ImGuiTableFlags_NoSavedSettings) + return; + + // Bind or create settings data + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = TableFindSettings(table); + if (settings == NULL) + { + settings = CreateTableSettings(table->ID, table->ColumnsCount); + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } + settings->ColumnsCount = (ImS8)table->ColumnsCount; + + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings --> ImGuiTable/ImGuiTableColumn + IM_ASSERT(settings->ID == table->ID); + IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount); + ImGuiTableColumn* column = table->Columns.Data; + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + + // FIXME-TABLE: Logic to avoid saving default widths? + settings->SaveFlags = ImGuiTableFlags_Resizable; + for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) + { + //column_settings->WidthOrWeight = column->WidthRequested; // FIXME-WIP + column_settings->Index = (ImS8)n; + column_settings->DisplayOrder = column->IndexDisplayOrder; + column_settings->SortOrder = column->SortOrder; + column_settings->SortDirection = column->SortDirection; + column_settings->Visible = column->IsActive; + + // We skip saving some data in the .ini file when they are unnecessary to restore our state + // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet. + if (column->IndexDisplayOrder != n) + settings->SaveFlags |= ImGuiTableFlags_Reorderable;; + if (column_settings->SortOrder != -1) + settings->SaveFlags |= ImGuiTableFlags_Sortable; + if (column_settings->Visible != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) + settings->SaveFlags |= ImGuiTableFlags_Hideable; + } + settings->SaveFlags &= table->Flags; + + MarkIniSettingsDirty(); +} + +void ImGui::TableLoadSettings(ImGuiTable* table) +{ + ImGuiContext& g = *GImGui; + table->IsSettingsRequestLoad = false; + if (table->Flags & ImGuiTableFlags_NoSavedSettings) + return; + + // Bind settings + ImGuiTableSettings* settings; + if (table->SettingsOffset == -1) + { + settings = FindTableSettingsByID(table->ID); + if (settings == NULL) + return; + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } + else + { + settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + } + table->IsSettingsLoaded = true; + settings->SaveFlags = table->Flags; + + // Serialize ImGuiTable/ImGuiTableColumn --> ImGuiTableSettings/ImGuiTableColumnSettings + ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) + { + int column_n = column_settings->Index; + if (column_n < 0 || column_n >= table->ColumnsCount) + continue; + ImGuiTableColumn* column = &table->Columns[column_n]; + //column->WidthRequested = column_settings->WidthOrWeight; // FIXME-WIP + if (column_settings->DisplayOrder != -1) + column->IndexDisplayOrder = column_settings->DisplayOrder; + if (column_settings->SortOrder != -1) + { + column->SortOrder = column_settings->SortOrder; + column->SortDirection = column_settings->SortDirection; + } + column->IsActive = column->NextIsActive = column_settings->Visible; + } + + // FIXME-TABLE: Need to validate .ini data + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->DisplayOrder[table->Columns[column_n].IndexDisplayOrder] = (ImU8)column_n; +} + +void* ImGui::TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + ImGuiID id = 0; + int columns_count = 0; + if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) + return NULL; + return CreateTableSettings(id, columns_count); +} + +void ImGui::TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +{ + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; + int column_n = 0, r = 0, n = 0; + if (sscanf(line, "Column %d%n", &column_n, &r) == 1) { line = ImStrSkipBlank(line + r); } else { return; } + if (column_n < 0 || column_n >= settings->ColumnsCount) + return; + + char c = 0; + ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; + column->Index = (ImS8)column_n; + if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); /* .. */ settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->Visible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } + if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImS8)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } + if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } +} + +void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +{ + ImGuiContext& g = *ctx; + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + { + if (settings->ID == 0) // Skip ditched settings + continue; + + // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped (e.g. Order was unchanged) + const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; + const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; + const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; + const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; + if (!save_size && !save_visible && !save_order && !save_sort) + continue; + + buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve + buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); + ImGuiTableColumnSettings* column = settings->GetColumnSettings(); + for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) + { + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + if (column->UserID != 0) + buf->appendf("Column %-2d UserID=%08X", column_n, column->UserID); + else + buf->appendf("Column %-2d", column_n); + if (save_size) buf->appendf(" Width=%d", 0);// (int)settings_column->WidthOrWeight); // FIXME-TABLE + if (save_visible) buf->appendf(" Visible=%d", column->Visible); + if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); + if (save_sort && column->SortOrder != -1) buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); + buf->append("\n"); + } + buf->append("\n"); + } +} + +//------------------------------------------------------------------------- +// TABLE - Debugging +//------------------------------------------------------------------------- +// - DebugNodeTable() [Internal] +//------------------------------------------------------------------------- + +void ImGui::DebugNodeTable(ImGuiTable* table) +{ + char buf[256]; + char* p = buf; + const char* buf_end = buf + IM_ARRAYSIZE(buf); + ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); + bool open = TreeNode(table, "%s", buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + if (open) + { + for (int n = 0; n < table->ColumnsCount; n++) + { + ImGuiTableColumn* column = &table->Columns[n]; + const char* name = TableGetColumnName(table, n); + BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" + "Active: %d, DrawChannels: %d,%d\n" + "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" + "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", + n, column->IndexDisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, + column->IsActive, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, + column->WidthGiven, column->WidthRequested, column->ResizeWeight, + column->UserID, column->Flags, + (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", + (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", + (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "", + (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); + } + ImGuiTableSettings* settings = TableFindSettings(table); + if (settings && TreeNode("Settings")) + { + BulletText("SaveFlags: 0x%08X", settings->SaveFlags); + BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); + for (int n = 0; n < settings->ColumnsCount; n++) + { + ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; + BulletText("Column %d Order %d SortOrder %d Visible %d UserID 0x%08X WidthOrWeight %.3f", + n, column_settings->DisplayOrder, column_settings->SortOrder, column_settings->Visible, column_settings->UserID, column_settings->WidthOrWeight); + } + TreePop(); + } + TreePop(); + } +} + +//------------------------------------------------------------------------- + + + //------------------------------------------------------------------------- // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3a30847d..6954960b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5999,6 +5999,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // which would be advantageous since most selectable are not selected. if (span_all_columns && window->DC.CurrentColumns) PushColumnsBackground(); + else if ((flags & ImGuiSelectableFlags_SpanAllColumns) && g.CurrentTable) + PushTableBackground(); // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries ImGuiButtonFlags button_flags = 0; @@ -6047,6 +6049,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (span_all_columns && window->DC.CurrentColumns) PopColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + PopTableBackground(); if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); From a09954bdaf037fdf28ad90f93c38bf0c4a636306 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 19 Dec 2019 14:52:18 +0100 Subject: [PATCH 004/144] Tables: Initial demo code. --- imgui_demo.cpp | 971 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 971 insertions(+) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 71fe735f..89320593 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -132,6 +132,15 @@ Index of this file: #define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) #define IM_CLAMP(V, MN, MX) ((V) < (MN) ? (MN) : (V) > (MX) ? (MX) : (V)) +// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall +#ifndef IMGUI_CDECL +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif +#endif + //----------------------------------------------------------------------------- // [SECTION] Forward Declarations, Helpers //----------------------------------------------------------------------------- @@ -206,6 +215,7 @@ void ImGui::ShowUserGuide() // - ShowDemoWindowWidgets() // - ShowDemoWindowLayout() // - ShowDemoWindowPopups() +// - ShowDemoWindowTables() // - ShowDemoWindowColumns() // - ShowDemoWindowMisc() //----------------------------------------------------------------------------- @@ -215,6 +225,7 @@ void ImGui::ShowUserGuide() static void ShowDemoWindowWidgets(); static void ShowDemoWindowLayout(); static void ShowDemoWindowPopups(); +static void ShowDemoWindowTables(); static void ShowDemoWindowColumns(); static void ShowDemoWindowMisc(); @@ -472,6 +483,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ShowDemoWindowWidgets(); ShowDemoWindowLayout(); ShowDemoWindowPopups(); + ShowDemoWindowTables(); ShowDemoWindowColumns(); ShowDemoWindowMisc(); @@ -3211,6 +3223,962 @@ static void ShowDemoWindowPopups() } } +// Dummy data structure that we use for the Table demo. +// (pre-C++11 doesn't allow us to instantiate ImVector template if this structure if defined inside the demo function) +namespace +{ +// We are passing our own identifier to TableSetupColumn() to facilitate identifying columns in the sorting code. +// This identifier will be passed down into ImGuiTableSortSpec::ColumnUserID. +// But it is possible to omit the user id parameter of TableSetupColumn() and just use the column index instead! (ImGuiTableSortSpec::ColumnIndex) +// If you don't use sorting, you will generally never care about giving column an ID! +enum MyItemColumnID +{ + MyItemColumnID_ID, + MyItemColumnID_Name, + MyItemColumnID_Action, + MyItemColumnID_Quantity, + MyItemColumnID_Description +}; + +struct MyItem +{ + int ID; + const char* Name; + int Quantity; + + // We have a problem which is affecting _only this demo_ and should not affect your code: + // As we don't rely on std:: or other third-party library to compile dear imgui, we only have reliable access to qsort(), + // however qsort doesn't allow passing user data to comparing function. + // As a workaround, we are storing the sort specs in a static/global for the comparing function to access. + // In your own use case you would probably pass the sort specs to your sorting/comparing functions directly and not use a global. + static const ImGuiTableSortSpecs* s_current_sort_specs; + + // Compare function to be used by qsort() + static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) + { + const MyItem* a = (const MyItem*)lhs; + const MyItem* b = (const MyItem*)rhs; + for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) + { + // Here we identify columns using the ColumnUserID value that we ourselves passed to TableSetupColumn() + // We could also choose to identify columns based on their index (sort_spec->ColumnIndex), which is simpler! + const ImGuiTableSortSpecsColumn* sort_spec = &s_current_sort_specs->Specs[n]; + int delta = 0; + switch (sort_spec->ColumnUserID) + { + case MyItemColumnID_ID: delta = (a->ID - b->ID); break; + case MyItemColumnID_Name: delta = (strcmp(a->Name, b->Name)); break; + case MyItemColumnID_Quantity: delta = (a->Quantity - b->Quantity); break; + case MyItemColumnID_Description: delta = (strcmp(a->Name, b->Name)); break; + default: IM_ASSERT(0); break; + } + if (delta < 0) + return -1 * sort_spec->SortSign; + if (delta > 0) + return +1 * sort_spec->SortSign; + } + + // qsort() is instable so always return a way to differenciate items. + // Your own compare function may want to avoid fallback on implicit sort specs e.g. a Name compare if it wasn't already part of the sort specs. + return (a->ID - b->ID); + } +}; +const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; +} + +static void ShowDemoWindowTables() +{ + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (!ImGui::CollapsingHeader("Tables")) + return; + + ImGui::PushID("Tables"); + + int open_action = -1; + if (ImGui::Button("Open all")) + open_action = 1; + ImGui::SameLine(); + if (ImGui::Button("Close all")) + open_action = 0; + ImGui::SameLine(); + + // Options + static bool disable_indent = false; + ImGui::Checkbox("Disable tree indentation", &disable_indent); + ImGui::SameLine(); + HelpMarker("Disable the indenting of tree nodes so demo tables can use the full window width."); + ImGui::Separator(); + if (disable_indent) + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f); + + // About Styling of tables + // Most settings are configured on a per-table basis via the flags passed to BeginTable() and TableSetupColumns APIs. + // There are however a few settings that a shared and part of the ImGuiStyle structure: + // style.CellPadding // Padding within each cell + // style.Colors[ImGuiCol_TableHeaderBg] // Table header background + // style.Colors[ImGuiCol_TableRowBg] // Table row background when ImGuiTableFlags_RowBg is enabled (even rows) + // style.Colors[ImGuiCol_TableRowBgAlt] // Table row background when ImGuiTableFlags_RowBg is enabled (odds rows) + + // Demos + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Basic")) + { + // Here we will showcase 4 different ways to output a table. They are very simple variations of a same thing! + + // Basic use of tables using TableNextRow() to create a new row, and TableSetColumnIndex() to select the column. + // In many situations, this is the most flexible and easy to use pattern. + HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, in a loop."); + if (ImGui::BeginTable("##table1", 3)) + { + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Row %d Column %d", row, column); + } + } + ImGui::EndTable(); + } + + // This essentially the same as above, except instead of using a for loop we call TableSetColumnIndex() manually. + // Sometimes this makes more sense. + HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, manually."); + if (ImGui::BeginTable("##table2", 3)) + { + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Row %d", row); + ImGui::TableSetColumnIndex(1); + ImGui::Text("Some contents"); + ImGui::TableSetColumnIndex(2); + ImGui::Text("123.456"); + } + ImGui::EndTable(); + } + + // Another subtle variant, we call TableNextCell() _before_ each cell. At the end of a row, TableNextCell() will create a new row. + // Note that we don't call TableNextRow() here! + // If we want to call TableNextRow(), then we don't need to call TableNextCell() for the first cell. + HelpMarker("Only using TableNextCell(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); + if (ImGui::BeginTable("##table4", 3)) + { + for (int item = 0; item < 14; item++) + { + ImGui::TableNextCell(); + ImGui::Text("Item %d", item); + } + ImGui::EndTable(); + } + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("With borders, background")) + { + // Expose a few Borders related flags interactively + static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; + static bool display_width = false; + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&flags, ImGuiTableFlags_Borders); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersOuter\n | ImGuiTableFlags_BordersV\n | ImGuiTableFlags_BordersH"); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::Checkbox("Debug Display width", &display_width); + + if (ImGui::BeginTable("##table1", 3, flags)) + { + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + if (display_width) + { + ImVec2 p = ImGui::GetCursorScreenPos(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + float x1 = p.x; + float x2 = ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x; + float x3 = draw_list->GetClipRectMax().x; + float y2 = p.y + ImGui::GetTextLineHeight(); + draw_list->AddLine(ImVec2(x1, y2), ImVec2(x3, y2), IM_COL32(255, 255, 0, 255)); // Hard clipping limit + draw_list->AddLine(ImVec2(x1, y2), ImVec2(x2, y2), IM_COL32(255, 0, 0, 255)); // Normal limit + ImGui::Text("w=%.2f", x2 - x1); + } + else + { + ImGui::Text("Hello %d,%d", row, column); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Resizable, stretch")) + { + // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" + // Each columns maintain a sizing weight, and they will occupy all available width. + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersV flag as well."); + + if (ImGui::BeginTable("##table1", 3, flags)) + { + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", row, column); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Resizable, fixed")) + { + // Here we use ImGuiTableFlags_SizingPolicyFixedX (even though _ScrollX is not set) + // So columns will adopt the "Fixed" policy and will maintain a fixed weight regardless of the whole available width. + // If there is not enough available width to fit all columns, they will however be resized down. + // FIXME-TABLE: Providing a stretch-on-init would make sense especially for tables which don't have saved settings + HelpMarker("Using _Resizable + _SizingPolicyFixedX flags.\nFixed-width columns generally makes more sense if you want to use horizontal scrolling."); + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); // FIXME-TABLE: Explain or fix the effect of enable Scroll on outer_size + if (ImGui::BeginTable("##table1", 3, flags)) + { + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", row, column); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Resizable, mixed")) + { + HelpMarker("Using columns flag to alter resizing policy on a per-column basis."); + static ImGuiTableFlags flags = ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); // FIXME-TABLE: Explain or fix the effect of enable Scroll on outer_size + + if (ImGui::BeginTable("##table1", 3, flags, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6))) + { + ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed);// | ImGuiTableColumnFlags_NoResize); + ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableAutoHeaders(); + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %d,%d", (column == 2) ? "Stretch" : "Fixed", row, column); + } + } + ImGui::EndTable(); + } + if (ImGui::BeginTable("##table2", 6, flags, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6))) + { + ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("DDD", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("EEE", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("FFF", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultHide); + ImGui::TableAutoHeaders(); + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 6; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %d,%d", (column >= 3) ? "Stretch" : "Fixed", row, column); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Reorderable, hideable, with headers")) + { + HelpMarker("Click and drag column headers to reorder columns.\n\nYou can also right-click on a header to open a context menu."); + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); + + if (ImGui::BeginTable("##table1", 3, flags)) + { + // Submit columns name with TableSetupColumn() and call TableAutoHeaders() to create a row with a header in each column. + // (Later we will show how TableSetupColumn() has other uses, optional flags, sizing weight etc.) + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableAutoHeaders(); + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", row, column); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Vertical scrolling, with clipping")) + { + HelpMarker("Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\nWe also demonstrate using ImGuiListClipper to virtualize the submission of many items."); + ImVec2 size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 7); + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); + + if (ImGui::BeginTable("##table1", 3, flags, size)) + { + ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); + ImGui::TableAutoHeaders(); + ImGuiListClipper clipper; + clipper.Begin(1000); + while (clipper.Step()) + { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", row, column); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Horizontal scrolling")) + { + HelpMarker("When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingPolicyFixedX, as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\nAlso note that as of the current version, you will almost always want to enable ScrollY along with ScrollX, because the container window won't automatically extend vertically to fix contents (this may be improved in future versions)."); + ImVec2 size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 10); + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_ScrollFreezeLeftColumn | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); + + if (ImGui::BeginTable("##table1", 7, flags, size)) + { + ImGui::TableSetupColumn("Line #", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of ImGuiTableFlags_ScrollFreezeLeftColumn + ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Four", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Five", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Six", ImGuiTableColumnFlags_None); + ImGui::TableAutoHeaders(); + for (int row = 0; row < 20; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 7; column++) + { + ImGui::TableSetColumnIndex(column); + if (column == 0) + ImGui::Text("Line %d", row); + else + ImGui::Text("Hello world %d,%d", row, column); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Columns flags")) + { + // Create a first table just to show all the options/flags we want to make visible in our example! + const int column_count = 3; + const char* column_names[column_count] = { "One", "Two", "Three" }; + static ImGuiTableColumnFlags column_flags[column_count] = { ImGuiTableColumnFlags_DefaultSort, ImGuiTableColumnFlags_None, ImGuiTableColumnFlags_DefaultHide }; + + if (ImGui::BeginTable("##flags", column_count, ImGuiTableFlags_None)) + { + for (int column = 0; column < column_count; column++) + { + ImGui::TableNextCell(); + // Make the UI compact because there are so many fields + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 2)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); + ImGui::PushID(column); + ImGui::AlignTextToFramePadding(); // FIXME-TABLE: Workaround for wrong text baseline propagation + ImGui::Text("Column '%s'", column_names[column]); + ImGui::CheckboxFlags("_NoResize", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoResize); + ImGui::CheckboxFlags("_NoClipX", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoClipX); + ImGui::CheckboxFlags("_NoHide", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoHide); + ImGui::CheckboxFlags("_DefaultSort", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_DefaultSort); + ImGui::CheckboxFlags("_DefaultHide", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_DefaultHide); + ImGui::CheckboxFlags("_NoSort", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSort); + ImGui::CheckboxFlags("_NoSortAscending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSortAscending); + ImGui::CheckboxFlags("_NoSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSortDescending); + ImGui::CheckboxFlags("_PreferSortAscending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortAscending); + ImGui::CheckboxFlags("_PreferSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortDescending); + ImGui::PopID(); + ImGui::PopStyleVar(2); + } + ImGui::EndTable(); + } + + // Create the real table we care about for the example! + const ImGuiTableFlags flags = ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable; + if (ImGui::BeginTable("##table1", column_count, flags)) + { + for (int column = 0; column < column_count; column++) + ImGui::TableSetupColumn(column_names[column], column_flags[column]); + ImGui::TableAutoHeaders(); + for (int row = 0; row < 8; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < column_count; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %s", ImGui::TableGetColumnName(column)); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Recursive")) + { + HelpMarker("This demonstrate embedding a table into another table cell."); + + if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + { + ImGui::TableSetupColumn("A0"); + ImGui::TableSetupColumn("A1"); + ImGui::TableAutoHeaders(); + + ImGui::TableNextRow(); ImGui::Text("A0 Cell 0"); + { + float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; + if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + { + ImGui::TableSetupColumn("B0"); + ImGui::TableSetupColumn("B1"); + ImGui::TableAutoHeaders(); + + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::Text("B0 Cell 0"); + ImGui::TableNextCell(); + ImGui::Text("B0 Cell 1"); + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::Text("B1 Cell 0"); + ImGui::TableNextCell(); + ImGui::Text("B1 Cell 1"); + + ImGui::EndTable(); + } + } + ImGui::TableNextCell(); ImGui::Text("A0 Cell 1"); + ImGui::TableNextRow(); ImGui::Text("A1 Cell 0"); + ImGui::TableNextCell(); ImGui::Text("A1 Cell 1"); + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Sizing policies, cell contents")) + { + HelpMarker("This section allows you to interact and see the effect of StretchX vs FixedX sizing policies depending on whether Scroll is enabled and the contents of your columns."); + enum ContentsType { CT_ShortText, CT_LongText, CT_Button, CT_StretchButton, CT_InputText }; + static int contents_type = CT_ShortText; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12); + ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Stretch Button\0InputText\0"); + + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) + flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyStretchX); // Can't specify both sizing polices so we clear the other + ImGui::SameLine(); HelpMarker("Default if _ScrollX if disabled."); + if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyFixedX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyFixedX)) + flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyFixedX); // Can't specify both sizing polices so we clear the other + ImGui::SameLine(); HelpMarker("Default if _ScrollX if enabled."); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClipX", (unsigned int*)&flags, ImGuiTableFlags_NoClipX); + + if (ImGui::BeginTable("##3ways", 3, flags, ImVec2(0, 100))) + { + for (int row = 0; row < 10; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + char label[32]; + static char text_buf[32] = ""; + sprintf(label, "Hello %d,%d", row, column); + switch (contents_type) + { + case CT_ShortText: ImGui::TextUnformatted(label); break; + case CT_LongText: ImGui::Text("Some longer text %d,%d\nOver two lines..", row, column); break; + case CT_Button: ImGui::Button(label); break; + case CT_StretchButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; + case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText(label, text_buf, IM_ARRAYSIZE(text_buf)); break; + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Compact table")) + { + // FIXME-TABLE: Vertical border not overridden the same way as horizontal one + HelpMarker("Setting style.CellPadding to (0,0)."); + + static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); + + static bool no_widget_frame = false; + ImGui::Checkbox("no_widget_frame", &no_widget_frame); + + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); + if (ImGui::BeginTable("##3ways", 3, flags)) + { + for (int row = 0; row < 10; row++) + { + static char text_buf[32] = ""; + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::PushID(row * 3 + column); + if (no_widget_frame) + ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); + ImGui::InputText("##cell", text_buf, IM_ARRAYSIZE(text_buf)); + if (no_widget_frame) + ImGui::PopStyleColor(); + ImGui::PopID(); + } + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + + static const char* template_items_names[] = + { + "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", + "Kiwi", "Orange", "Pineapple", "Blueberry", "Plum", "Coconut", "Pear", "Apricot" + }; + + // This is a simplified version of the "Advanced" example, where we mostly focus on the code necessary to handle sorting. + // Note that the "Advanced" example also showcase manually triggering a sort (e.g. if item quantities have been modified) + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Sorting")) + { + HelpMarker("Use Shift+Click to sort on multiple columns"); + + // Create item list + static ImVector items; + if (items.Size == 0) + { + items.resize(50, MyItem()); + for (int n = 0; n < items.Size; n++) + { + const int template_n = n % IM_ARRAYSIZE(template_items_names); + MyItem& item = items[n]; + item.ID = n; + item.Name = template_items_names[template_n]; + item.Quantity = (n * n - n) % 20; // Assign default quantities + } + } + + static ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable + | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV + | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow; + if (ImGui::BeginTable("##table", 4, flags, ImVec2(0, 250), 0.0f)) + { + // Declare columns + // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. + // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + // Demonstrate using a mixture of flags among available sort-related flags: + // - ImGuiTableColumnFlags_DefaultSort + // - ImGuiTableColumnFlags_NoSort / ImGuiTableColumnFlags_NoSortAscending / ImGuiTableColumnFlags_NoSortDescending + // - ImGuiTableColumnFlags_PreferSortAscending / ImGuiTableColumnFlags_PreferSortDescending + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Name); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); + ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, -1.0f, MyItemColumnID_Quantity); + + // Sort our data if sort specs have been changed! + if (const ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + if (sorts_specs->SpecsChanged && items.Size > 1) + { + MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. + qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + } + + // Display data + ImGui::TableAutoHeaders(); + ImGuiListClipper clipper; + clipper.Begin(items.Size); + while (clipper.Step()) + for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) + { + MyItem* item = &items[row_n]; + ImGui::PushID(item->ID); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("%04d", item->ID); + ImGui::TableSetColumnIndex(1); + ImGui::TextUnformatted(item->Name); + ImGui::TableSetColumnIndex(2); + ImGui::SmallButton("None"); + ImGui::TableSetColumnIndex(3); + ImGui::Text("%d", item->Quantity); + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Advanced")) + { + static ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable + | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders + | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY + | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_ScrollFreezeLeftColumn + | ImGuiTableFlags_SizingPolicyFixedX + ; + + enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_Selectable }; + static int contents_type = CT_Button; + const char* contents_type_names[] = { "Text", "Button", "SmallButton", "Selectable" }; + + static int items_count = IM_ARRAYSIZE(template_items_names); + static ImVec2 outer_size_value = ImVec2(0, 250); + static float row_min_height = 0.0f; // Auto + static float inner_width_without_scroll = 0.0f; // Fill + static float inner_width_with_scroll = 0.0f; // Auto-extend + static bool outer_size_enabled = true; + static bool lock_left_column_visibility = false; + static bool show_headers = true; + static bool show_wrapped_text = false; + static ImGuiTextFilter filter; + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which affects sizing + if (ImGui::TreeNodeEx("Options")) + { + // Make the UI compact because there are so many fields + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 1)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); + ImGui::PushItemWidth(200); + + ImGui::BulletText("Features:"); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_Sortable", (unsigned int*)&flags, ImGuiTableFlags_Sortable); + ImGui::CheckboxFlags("ImGuiTableFlags_MultiSortable", (unsigned int*)&flags, ImGuiTableFlags_MultiSortable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoSavedSettings", (unsigned int*)&flags, ImGuiTableFlags_NoSavedSettings); + ImGui::Unindent(); + + ImGui::BulletText("Decoration:"); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersFullHeight", (unsigned int*)&flags, ImGuiTableFlags_BordersFullHeight); + ImGui::Unindent(); + + ImGui::BulletText("Padding, Sizing:"); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClipX", (unsigned int*)&flags, ImGuiTableFlags_NoClipX); + if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) + flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyStretchX); // Can't specify both sizing polices so we clear the other + ImGui::SameLine(); HelpMarker("[Default if ScrollX is off]\nFit all columns within available width (or specified inner_width). Fixed and Stretch columns allowed."); + if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyFixedX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyFixedX)) + flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyFixedX); // Can't specify both sizing polices so we clear the other + ImGui::SameLine(); HelpMarker("[Default if ScrollX is on]\nEnlarge as needed: enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rectangle. Only Fixed columns allowed. Stretched columns will calculate their width assuming no scrolling."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHeadersWidth", (unsigned int*)&flags, ImGuiTableFlags_NoHeadersWidth); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", (unsigned int*)&flags, ImGuiTableFlags_NoHostExtendY); + ImGui::Unindent(); + + ImGui::BulletText("Scrolling:"); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + + // For the purpose of our "advanced" demo, we expose the 3 freezing variants on both axises instead of only exposing the most common flag. + //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); + //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); + int freeze_row_count = (flags & ImGuiTableFlags_ScrollFreezeRowsMask_) >> ImGuiTableFlags_ScrollFreezeRowsShift_; + int freeze_col_count = (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) >> ImGuiTableFlags_ScrollFreezeColumnsShift_; + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + if (ImGui::DragInt("ImGuiTableFlags_ScrollFreezeTopRow/2Rows/3Rows", &freeze_row_count, 0.2f, 0, 3)) + if (freeze_row_count >= 0 && freeze_row_count <= 3) + flags = (flags & ~ImGuiTableFlags_ScrollFreezeRowsMask_) | (freeze_row_count << ImGuiTableFlags_ScrollFreezeRowsShift_); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + if (ImGui::DragInt("ImGuiTableFlags_ScrollFreezeLeftColumn/2Columns/3Columns", &freeze_col_count, 0.2f, 0, 3)) + if (freeze_col_count >= 0 && freeze_col_count <= 3) + flags = (flags & ~ImGuiTableFlags_ScrollFreezeColumnsMask_) | (freeze_col_count << ImGuiTableFlags_ScrollFreezeColumnsShift_); + + ImGui::Unindent(); + + ImGui::BulletText("Other:"); + ImGui::Indent(); + ImGui::DragFloat2("##OuterSize", &outer_size_value.x); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Checkbox("outer_size", &outer_size_enabled); + ImGui::SameLine(); + HelpMarker("If scrolling is disabled (ScrollX and ScrollY not set), the table is output directly in the parent window. OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendV is set)."); + + // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. + // To facilitate experimentation we expose two values and will select the right one depending on active flags. + if (flags & ImGuiTableFlags_ScrollX) + ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); + else + ImGui::DragFloat("inner_width (when ScrollX inactive)", &inner_width_without_scroll, 1.0f, 0.0f, FLT_MAX); + ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); + ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); + ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000); + ImGui::Combo("contents_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); + filter.Draw("filter"); + ImGui::Checkbox("show_headers", &show_headers); + ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); + ImGui::Checkbox("lock_left_column_visibility", &lock_left_column_visibility); + ImGui::Unindent(); + + ImGui::PopItemWidth(); + ImGui::PopStyleVar(2); + ImGui::Spacing(); + ImGui::TreePop(); + } + + // Recreate/reset item list if we changed the number of items + static ImVector items; + static ImVector selection; + static bool items_need_sort = false; + if (items.Size != items_count) + { + items.resize(items_count, MyItem()); + for (int n = 0; n < items_count; n++) + { + const int template_n = n % IM_ARRAYSIZE(template_items_names); + MyItem& item = items[n]; + item.ID = n; + item.Name = template_items_names[template_n]; + item.Quantity = (template_n == 3) ? 10 : (template_n == 4) ? 20 : 0; // Assign default quantities + } + } + + const ImDrawList* parent_draw_list = ImGui::GetWindowDrawList(); + const int parent_draw_list_draw_cmd_count = parent_draw_list->CmdBuffer.Size; + + const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : inner_width_without_scroll; + if (ImGui::BeginTable("##table", 5, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) + { + // Declare columns + // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. + // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | (lock_left_column_visibility ? ImGuiTableColumnFlags_NoHide : 0), -1.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Name); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); + ImGui::TableSetupColumn("Quantity Long Label", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 1.0f, MyItemColumnID_Quantity);// , ImGuiTableColumnFlags_None | ImGuiTableColumnFlags_WidthAlwaysAutoResize); + ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 1.0f, MyItemColumnID_Description);// , ImGuiTableColumnFlags_WidthAlwaysAutoResize); + + // Sort our data if sort specs have been changed! + const ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); + if (sorts_specs && sorts_specs->SpecsChanged) + items_need_sort = true; + if (sorts_specs && items_need_sort && items.Size > 1) + { + MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. + qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + } + items_need_sort = false; + + // Take note of whether we are currently sorting based on the Quantity field, + // we will use this to trigger sorting when we know the data of this column has been modified. + const bool sorts_specs_using_quantity = ImGui::TableGetColumnIsSorted(3); + + // Show headers + if (show_headers) + ImGui::TableAutoHeaders(); + + // Show data + // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? + ImGui::PushButtonRepeat(true); +#if 1 + ImGuiListClipper clipper; + clipper.Begin(items.Size); + while (clipper.Step()) + { + for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) +#else + { + for (int row_n = 0; row_n < items_count; n++) +#endif + { + MyItem* item = &items[row_n]; + if (!filter.PassFilter(item->Name)) + continue; + + const bool item_is_selected = selection.contains(item->ID); + ImGui::PushID(item->ID); + ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); + + // For the demo purpose we can select among different type of items submitted in the first column + char label[32]; + sprintf(label, "%04d", item->ID); + if (contents_type == CT_Text) + ImGui::TextUnformatted(label); + else if (contents_type == CT_Button) + ImGui::Button(label); + else if (contents_type == CT_SmallButton) + ImGui::SmallButton(label); + else if (contents_type == CT_Selectable) + { + if (ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0, row_min_height))) + { + if (ImGui::GetIO().KeyCtrl) + { + if (item_is_selected) + selection.find_erase_unsorted(item->ID); + else + selection.push_back(item->ID); + } + else + { + selection.clear(); + selection.push_back(item->ID); + } + } + } + + ImGui::TableNextCell(); + ImGui::TextUnformatted(item->Name); + + // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity, + // and we are currently sorting on the column showing the Quantity. + // To avoid triggering a sort while holding the button, we only trigger it when the button has been released. + // You will probably need a more advanced system in your code if you want to automatically sort when a specific entry changes. + if (ImGui::TableNextCell()) + { + if (ImGui::SmallButton("Chop")) { item->Quantity += 1; } + if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } + ImGui::SameLine(); + if (ImGui::SmallButton("Eat")) { item->Quantity -= 1; } + if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } + } + + ImGui::TableNextCell(); + ImGui::Text("%d", item->Quantity); + + ImGui::TableNextCell(); + if (show_wrapped_text) + ImGui::TextWrapped("Lorem ipsum dolor sit amet"); + else + ImGui::Text("Lorem ipsum dolor sit amet"); + + ImGui::PopID(); + } + } + ImGui::PopButtonRepeat(); + + const ImVec2 table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); + const ImVec2 table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY()); + const ImDrawList* table_draw_list = ImGui::GetWindowDrawList(); + ImGui::EndTable(); + + static bool show_debug_details = false; + ImGui::Checkbox("Debug details", &show_debug_details); + if (show_debug_details) + { + ImGui::SameLine(0.0f, 0.0f); + const int table_draw_list_draw_cmd_count = table_draw_list->CmdBuffer.Size; + if (table_draw_list == parent_draw_list) + ImGui::Text(": DrawCmd: +%d (in same window)", table_draw_list_draw_cmd_count - parent_draw_list_draw_cmd_count); + else + ImGui::Text(": DrawCmd: +%d (in child window), Scroll: (%.f/%.f) (%.f/%.f)", + table_draw_list_draw_cmd_count - 1, table_scroll_cur.x, table_scroll_max.x, table_scroll_cur.y, table_scroll_max.y); + } + } + ImGui::TreePop(); + } + + if (disable_indent) + ImGui::PopStyleVar(); + ImGui::PopID(); +} + +// 2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful Tables API! static void ShowDemoWindowColumns() { if (!ImGui::CollapsingHeader("Columns")) @@ -3225,6 +4193,8 @@ static void ShowDemoWindowColumns() if (disable_indent) ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f); + ImGui::TextWrapped("Note: Columns are under-featured and not maintained. Prefer using the more flexible and powerful Tables API!"); + // Basic columns if (ImGui::TreeNode("Basic")) { @@ -3944,6 +4914,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); From e06a36ab120b69b91b6a66c12c7a925eb4b9f942 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 26 Dec 2019 11:15:56 +0100 Subject: [PATCH 005/144] Tables: Support for multiple Tables using same id where most settings are synced. (some minor one-frame lack of sync when e.g. toggling visibility in context menu) --- imgui_internal.h | 7 ++++- imgui_tables.cpp | 76 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 5ec83845..6cddf0c9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1891,6 +1891,8 @@ struct ImGuiTable int ColumnsActiveCount; // Number of non-hidden columns (<= ColumnsCount) int CurrentColumn; int CurrentRow; + ImS16 InstanceNo; // Count of BeginTable() calls with same ID in the same frame (generally 0) + ImS16 InstanceInteracted; float RowPosY1; float RowPosY2; float RowTextBaseline; @@ -1910,6 +1912,7 @@ struct ImGuiTable float LastFirstRowHeight; // Height of first row from last frame float ColumnsTotalWidth; float InnerWidth; + float ResizedColumnNextWidth; ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect WorkRect; ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. @@ -1925,7 +1928,8 @@ struct ImGuiTable ImS8 DeclColumnsCount; // Count calls to TableSetupColumn() ImS8 HoveredColumnBody; // [DEBUG] Unlike HoveredColumnBorder this doesn't fulfill all Hovering rules properly. Used for debugging/tools for now. ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). - ImS8 ResizedColumn; // Index of column being resized. + ImS8 ResizedColumn; // Index of column being resized. Reset by InstanceNo==0. + ImS8 HeadHeaderColumn; // Index of column header being held. ImS8 LastResizedColumn; ImS8 ReorderColumn; // Index of column being reordered. (not cleared) ImS8 ReorderColumnDir; // -1 or +1 @@ -1957,6 +1961,7 @@ struct ImGuiTable { memset(this, 0, sizeof(*this)); SettingsOffset = -1; + InstanceInteracted = -1; LastFrameActive = -1; LastResizedColumn = -1; ContextPopupColumn = -1; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 657ccad4..57b049ba 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -158,9 +158,8 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); - // If an outer size is specified ahead we will be able to early out when not visible, - // The exact rules here can evolve. - if (use_child_window && IsClippedEx(outer_rect, id, false)) + // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve. + if (use_child_window && IsClippedEx(outer_rect, 0, false)) { ItemSize(outer_rect); return false; @@ -173,9 +172,16 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags // Acquire storage for the table ImGuiTable* table = g.Tables.GetOrAddByKey(id); const ImGuiTableFlags table_last_flags = table->Flags; + const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceNo + 1; + const ImGuiID instance_id = id + instance_no; + if (instance_no > 0) + IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); + + // Initialize table->ID = id; table->Flags = flags; table->IsFirstFrame = (table->LastFrameActive == -1); + table->InstanceNo = (ImS16)instance_no; table->LastFrameActive = g.FrameCount; table->OuterWindow = table->InnerWindow = outer_window; table->ColumnsCount = columns_count; @@ -203,7 +209,7 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags // Create scrolling region (without border = zero window padding) ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; - BeginChildEx(str_id, id, table->OuterRect.GetSize(), false, child_flags); + BeginChildEx(str_id, instance_id, table->OuterRect.GetSize(), false, child_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); @@ -211,7 +217,7 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags else { // WorkRect.Max will grow as we append contents. - PushID(id); + PushID(instance_id); } const bool has_cell_padding_x = (flags & (ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) != 0; @@ -244,7 +250,6 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags table->HoveredColumnBody = -1; table->HoveredColumnBorder = -1; table->RightMostActiveColumn = -1; - table->LeftMostStretchedColumnDisplayOrder = -1; table->IsFirstFrame = false; // FIXME-TABLE FIXME-STYLE: Using opaque colors facilitate overlapping elements of the grid @@ -291,18 +296,36 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags if (table->IsFirstFrame || table->IsSettingsRequestLoad) TableLoadSettings(table); + // Handle resizing request + // (We process this at the first beginning of the frame) + // FIXME-TABLE: Preserve contents width _while resizing down_ until releasing. + // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling. + if (table->InstanceNo == 0) + { + if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) + TableSetColumnWidth(table, &table->Columns[table->ResizedColumn], table->ResizedColumnNextWidth); + table->ResizedColumnNextWidth = FLT_MAX; + table->ResizedColumn = -1; + } + // Handle reordering request // Note: we don't clear ReorderColumn after handling the request. - if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) + if (table->InstanceNo == 0) { - IM_ASSERT(table->ReorderColumnDir == -1 || table->ReorderColumnDir == +1); - IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); - ImGuiTableColumn* dragged_column = &table->Columns[table->ReorderColumn]; - ImGuiTableColumn* target_column = &table->Columns[(table->ReorderColumnDir == -1) ? dragged_column->PrevActiveColumn : dragged_column->NextActiveColumn]; - ImSwap(table->DisplayOrder[dragged_column->IndexDisplayOrder], table->DisplayOrder[target_column->IndexDisplayOrder]); - ImSwap(dragged_column->IndexDisplayOrder, target_column->IndexDisplayOrder); - table->ReorderColumnDir = 0; - table->IsSettingsDirty = true; + if (table->HeadHeaderColumn == -1 && table->ReorderColumn != -1) + table->ReorderColumn = -1; + table->HeadHeaderColumn = -1; + if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) + { + IM_ASSERT(table->ReorderColumnDir == -1 || table->ReorderColumnDir == +1); + IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); + ImGuiTableColumn* dragged_column = &table->Columns[table->ReorderColumn]; + ImGuiTableColumn* target_column = &table->Columns[(table->ReorderColumnDir == -1) ? dragged_column->PrevActiveColumn : dragged_column->NextActiveColumn]; + ImSwap(table->DisplayOrder[dragged_column->IndexDisplayOrder], table->DisplayOrder[target_column->IndexDisplayOrder]); + ImSwap(dragged_column->IndexDisplayOrder, target_column->IndexDisplayOrder); + table->ReorderColumnDir = 0; + table->IsSettingsDirty = true; + } } // Handle display order reset request @@ -713,7 +736,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsUsingHeaders = false; // Context menu - if (table->IsContextPopupOpen) + if (table->IsContextPopupOpen && table->InstanceNo == table->InstanceInteracted) { if (BeginPopup("##TableContextMenu")) { @@ -763,7 +786,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) continue; - ImGuiID column_id = table->ID + (ImGuiID)column_n; + ImGuiID column_id = table->ID + (table->InstanceNo * table->ColumnsCount) + column_n; ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, hit_y2); //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); KeepAliveID(column_id); @@ -771,7 +794,10 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) bool hovered = false, held = false; ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); if (held) + { table->ResizedColumn = (ImS8)column_n; + table->InstanceInteracted = table->InstanceNo; + } if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) { table->HoveredColumnBorder = (ImS8)column_n; @@ -861,14 +887,12 @@ void ImGui::EndTable() } // Apply resizing/dragging at the end of the frame - // FIXME-TABLE: Preserve contents width _while resizing down_ until releasing. - // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling. if (table->ResizedColumn != -1) { ImGuiTableColumn* column = &table->Columns[table->ResizedColumn]; const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS); const float new_width = ImFloor(new_x2 - column->MinX); - TableSetColumnWidth(table, column, new_width); + table->ResizedColumnNextWidth = new_width; } // Layout in outer window @@ -940,7 +964,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const int column_n = table->DisplayOrder[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; const bool is_hovered = (table->HoveredColumnBorder == column_n); - const bool is_resized = (table->ResizedColumn == column_n); + const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceNo); const bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); if (draw_right_border && column->MaxX > column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. { @@ -1724,7 +1748,7 @@ void ImGui::TableAutoHeaders() // [DEBUG] //if (g.IO.KeyCtrl) { static char buf[32]; name = buf; ImGuiTableColumn* c = &table->Columns[column_n]; if (c->Flags & ImGuiTableColumnFlags_WidthStretch) ImFormatString(buf, 32, "%.3f>%.1f", c->ResizeWeight, c->WidthGiven); else ImFormatString(buf, 32, "%.1f", c->WidthGiven); } - PushID(column_n); // Allow unnamed labels (generally accidental, but let's behave nicely with them) + PushID(table->InstanceNo * table->ColumnsCount + column_n); // Allow unnamed labels (generally accidental, but let's behave nicely with them) TableHeader(name); PopID(); @@ -1767,6 +1791,7 @@ void ImGui::TableAutoHeaders() { table->IsContextPopupOpen = true; table->ContextPopupColumn = (ImS8)open_context_popup; + table->InstanceInteracted = table->InstanceNo; OpenPopup("##TableContextMenu"); } } @@ -1807,9 +1832,11 @@ void ImGui::TableHeader(const char* label) //window->DC.CursorPos.x = column->MinX + table->CellPadding.x; // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) - const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n); + const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceNo); const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, row_height)); const bool held = IsItemActive(); + if (held) + table->HeadHeaderColumn = (ImS8)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; // Drag and drop: re-order columns. Frozen columns are not reorderable. @@ -1818,6 +1845,7 @@ void ImGui::TableHeader(const char* label) { // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x table->ReorderColumn = (ImS8)column_n; + table->InstanceInteracted = table->InstanceNo; if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) if (column->PrevActiveColumn != -1 && (column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (table->Columns[column->PrevActiveColumn].IndexWithinActiveSet < table->FreezeColumnsRequest)) table->ReorderColumnDir = -1; @@ -1863,8 +1891,6 @@ void ImGui::TableHeader(const char* label) if (pressed && table->ReorderColumn != column_n) TableSortSpecsClickColumn(table, column, g.IO.KeyShift); } - if (!held && table->ReorderColumn == column_n) - table->ReorderColumn = -1; // Render clipped label // Clipping here ensure that in the majority of situations, all our header cells will be merged into a single draw call. From eee82e0451047ce15ab8d7ed8532c50ba2208580 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 28 Dec 2019 17:45:15 +0100 Subject: [PATCH 006/144] Tables: Columns with no policy in a scrolling table will default to WidthFixed instead of WidthAlwaysAutoResize if an explicit value is passed to TableSetupColumn() --- imgui_tables.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 57b049ba..d545b45a 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -499,8 +499,8 @@ static float TableGetMinColumnWidth() // Layout columns for the frame // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. -// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAuto columns, -// increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution for WidthAuto columns? +// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAlwaysAutoResize columns, +// increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution for WidthAlwaysAutoResize columns? void ImGui::TableUpdateLayout(ImGuiTable* table) { IM_ASSERT(table->IsLayoutLocked == false); @@ -522,6 +522,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; // Adjust flags: default width mode + weighted columns are not allowed when auto extending + // FIXME-TABLE: Clarify why we need to do this again here and not just in TableSetupColumn() column->Flags = TableFixColumnFlags(table, column->FlagsIn); // We have a unusual edge case where if the user doesn't call TableGetSortSpecs() but has sorting enabled @@ -1269,6 +1270,12 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; table->DeclColumnsCount++; + // When passing a width automatically enforce WidthFixed policy (vs TableFixColumnFlags would default to WidthAlwaysAutoResize) + // (we write down to FlagsIn which is a little misleading, another solution would be to pass init_width_or_weight to TableFixColumnFlags) + if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) + if ((table->Flags & ImGuiTableFlags_SizingPolicyFixedX) && (init_width_or_weight > 0.0f)) + flags |= ImGuiTableColumnFlags_WidthFixed; + column->UserID = user_id; column->FlagsIn = flags; column->Flags = TableFixColumnFlags(table, column->FlagsIn); From 883c236edaf88ce3e030309c9352d1d21478b1e5 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 28 Dec 2019 19:17:59 +0100 Subject: [PATCH 007/144] Tables: Handle columns clipped due to host rect Return false in user functions, set SkipItems in window, redirect to dummy draw channel. --- imgui.cpp | 1 + imgui_demo.cpp | 4 +- imgui_internal.h | 10 +++-- imgui_tables.cpp | 106 ++++++++++++++++++++++++++++++----------------- 4 files changed, 78 insertions(+), 43 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c7be3624..660e2b55 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -11068,6 +11068,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImGuiWindow*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} +void ImGui::DebugNodeTable(ImGuiTable*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 89320593..7c0c4a7f 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3619,7 +3619,9 @@ static void ShowDemoWindowTables() ImGui::TableNextRow(); for (int column = 0; column < 7; column++) { - ImGui::TableSetColumnIndex(column); + // Both TableNextCell() and TableSetColumnIndex() return false when a column is not visible, which can be used for clipping. + if (!ImGui::TableSetColumnIndex(column)) + continue; if (column == 0) ImGui::Text("Line %d", row); else diff --git a/imgui_internal.h b/imgui_internal.h index 6cddf0c9..f5678f83 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1848,6 +1848,7 @@ struct ImGuiTableColumn ImS16 NameOffset; // Offset into parent ColumnsName[] bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. bool NextIsActive; + bool IsClipped; // Set when not overlapping the host window clipping rectangle. We don't use the opposite "!Visible" name because Clipped can be altered by events. ImS8 IndexDisplayOrder; // Index within DisplayOrder[] (column may be reordered by users) ImS8 IndexWithinActiveSet; // Index within active set (<= IndexOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] @@ -1855,7 +1856,8 @@ struct ImGuiTableColumn ImS8 DrawChannelRowsAfterFreeze; ImS8 PrevActiveColumn; // Index of prev active column within Columns[], -1 if first active column ImS8 NextActiveColumn; // Index of next active column within Columns[], -1 if last active column - ImS8 AutoFitFrames; + ImS8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit + ImS8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem ImS8 SortOrder; // -1: Not sorting on this column ImS8 SortDirection; // enum ImGuiSortDirection_ @@ -1869,7 +1871,7 @@ struct ImGuiTableColumn IndexDisplayOrder = IndexWithinActiveSet = -1; DrawChannelCurrent = DrawChannelRowsBeforeFreeze = DrawChannelRowsAfterFreeze = -1; PrevActiveColumn = NextActiveColumn = -1; - AutoFitFrames = 3; + AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames SortOrder = -1; SortDirection = ImGuiSortDirection_Ascending; } @@ -1884,6 +1886,7 @@ struct ImGuiTable ImVector DisplayOrder; // Store display order of columns (when not reordered, the values are 0...Count-1) ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map + ImU64 VisibleMaskByIndex; // Visible (== Active and not Clipped) ImGuiTableFlags SettingsSaveFlags; // Pre-compute which data we are going to save into the .ini file (e.g. when order is not altered we won't save order) int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; @@ -2179,7 +2182,7 @@ namespace ImGui //IMGUI_API bool SetTableColumnNo(int column_n); //IMGUI_API int GetTableLineNo(); IMGUI_API void TableBeginInitVisibility(ImGuiTable* table); - IMGUI_API void TableBeginInitDrawChannels(ImGuiTable* table); + IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); @@ -2194,6 +2197,7 @@ namespace ImGui IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API ImRect TableGetCellRect(); IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_no); + IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_no); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); IMGUI_API void TableLoadSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d545b45a..e847d477 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -65,13 +65,13 @@ // - BeginTable() user begin into a table // - BeginChild() - (if ScrollX/ScrollY is set) // - TableBeginInitVisibility() - lock columns visibility -// - TableBeginInitDrawChannels() - setup ImDrawList channels // - TableSetupColumn() user submit columns details (optional) // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) // - TableSortSpecsClickColumn() // - TableGetSortSpecs() user queries updated sort specs (optional) // - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() // - TableUpdateLayout() - called by the FIRST call to TableNextRow() +// - TableUpdateDrawChannels() - setup ImDrawList channels // - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission // - TableDrawContextMenu() - draw right-click context menu // - [...] user emit contents @@ -338,7 +338,6 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags } TableBeginInitVisibility(table); - TableBeginInitDrawChannels(table); // Grab a copy of window fields we will modify table->BackupSkipItems = inner_window->SkipItems; @@ -378,7 +377,7 @@ void ImGui::TableBeginInitVisibility(ImGuiTable* table) } if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_MultiSortable)) table->IsSortSpecsDirty = true; - if (column->AutoFitFrames > 0) + if (column->AutoFitQueue != 0x00) want_column_auto_fit = true; ImU64 index_mask = (ImU64)1 << column_n; @@ -405,6 +404,7 @@ void ImGui::TableBeginInitVisibility(ImGuiTable* table) } IM_ASSERT(column->IndexWithinActiveSet <= column->IndexDisplayOrder); } + table->VisibleMaskByIndex = table->ActiveMaskByIndex; // Columns will be masked out by TableUpdateLayout() when Clipped table->RightMostActiveColumn = (ImS8)(last_active_column ? table->Columns.index_from_ptr(last_active_column) : -1); // Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid @@ -413,7 +413,7 @@ void ImGui::TableBeginInitVisibility(ImGuiTable* table) table->InnerWindow->SkipItems = false; } -void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) +void ImGui::TableUpdateDrawChannels(ImGuiTable* table) { // Allocate draw channels. // - We allocate them following the storage order instead of the display order so reordering won't needlessly increase overall dormant memory cost @@ -428,7 +428,7 @@ void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClipX) ? 1 : table->ColumnsActiveCount; const int channels_for_background = 1; - const int channels_for_dummy = (table->ColumnsActiveCount < table->ColumnsCount) ? +1 : 0; + const int channels_for_dummy = (table->ColumnsActiveCount < table->ColumnsCount || table->VisibleMaskByIndex != table->ActiveMaskByIndex) ? +1 : 0; const int channels_total = channels_for_background + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total); table->DummyDrawChannel = channels_for_dummy ? (ImS8)(channels_total - 1) : -1; @@ -437,7 +437,7 @@ void ImGui::TableBeginInitDrawChannels(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->IsActive) + if (!column->IsClipped) { column->DrawChannelRowsBeforeFreeze = (ImS8)(draw_channel_current); column->DrawChannelRowsAfterFreeze = (ImS8)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row : 0)); @@ -534,7 +534,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { // Latch initial size for fixed columns count_fixed += 1; - const bool init_size = (column->AutoFitFrames > 0) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); + const bool init_size = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); if (init_size) { // Combine width from regular rows + width from headers unless requested not to @@ -546,7 +546,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // FIXME-TABLE: Increase minimum size during init frame so avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. // Otherwise what tends to happen is that TextWrapped would output a very large height (= first frame scrollbar display very off + clipper would skip lots of items) // This is merely making the side-effect less extreme, but doesn't properly fixes it. - if (column->AutoFitFrames > 1 && table->IsFirstFrame) + if (column->AutoFitQueue > 0x01 && table->IsFirstFrame) column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); } width_fixed += column->WidthRequested; @@ -559,10 +559,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (table->LeftMostStretchedColumnDisplayOrder == -1) table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->IndexDisplayOrder; } - - // Don't increment auto-fit until container window got a chance to submit its items - if (column->AutoFitFrames > 0 && table->BackupSkipItems == false) - column->AutoFitFrames--; } // Layout @@ -678,6 +674,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Max.x = offset_x; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); + column->IsClipped = true; continue; } @@ -693,27 +690,44 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->MinX = offset_x; column->MaxX = column->MinX + column->WidthGiven; - const float initial_max_pos_x = column->MinX + table->CellPaddingX1; - column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; - column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; - - // Starting cursor position - column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; - - // Alignment - // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. - // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) - //if (column->Flags & ImGuiTableColumnFlags_AlignRight) - // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]); - //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) - // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]), 0.5f); - //// A one pixel padding on the right side makes clipping more noticeable and contents look less cramped. column->ClipRect.Min.x = column->MinX; column->ClipRect.Min.y = work_rect.Min.y; column->ClipRect.Max.x = column->MaxX;// -1.0f; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); + + column->IsClipped = (column->ClipRect.Max.x <= column->ClipRect.Min.x) && (column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0; + if (column->IsClipped) + { + // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. + table->VisibleMaskByIndex &= ~((ImU64)1 << column_n); + } + else + { + // Starting cursor position + column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; + + // Alignment + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. + // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) + //if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]); + //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) + // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]), 0.5f); + + // Reset content width variables + const float initial_max_pos_x = column->MinX + table->CellPaddingX1; + column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; + column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; + } + + // Don't decrement auto-fit counters until container window got a chance to submit its items + if (table->BackupSkipItems == false) + { + column->AutoFitQueue >>= 1; + column->CannotSkipItemsQueue >>= 1; + } if (active_n < table->FreezeColumnsCount) host_clip_rect.Min.x = ImMax(host_clip_rect.Min.x, column->MaxX + 2.0f); @@ -727,6 +741,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) table->Flags &= ~ImGuiTableFlags_Resizable; + // Allocate draw channels + TableUpdateDrawChannels(table); + // Borders if (table->Flags & ImGuiTableFlags_Resizable) TableUpdateBorders(table); @@ -1067,7 +1084,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // - W1 F2 F3 resize from F2| --> FIXME should resize F2, F3 and not have effect on W1 (Stretch columns are _before_ the Fixed column). // Rules: - // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableSetupLayout(). + // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). // - [Resize Rule 2] Resizing from right-side of a Stretch column before a fixed column froward sizing to left-side of fixed column. // - [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure that our left border won't move. @@ -1188,7 +1205,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to fix... // cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." - // 2019/10/22: (2) Clamping code in TableSetupLayout() seemingly made this not necessary... + // 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary... #if 0 if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) merge_set_all_fit_within_inner_rect = false; @@ -1289,7 +1306,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) { column->WidthRequested = init_width_or_weight; - column->AutoFitFrames = 0; + column->AutoFitQueue = 0x00; } if (flags & ImGuiTableColumnFlags_WidthStretch) { @@ -1502,7 +1519,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) // FIXME-COLUMNS: Setup baseline, preserve across columns (how can we obtain first line baseline tho..) // window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); - window->SkipItems = column->IsActive ? table->BackupSkipItems : true; + window->SkipItems = column->IsClipped ? true : table->BackupSkipItems; if (table->Flags & ImGuiTableFlags_NoClipX) { table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); @@ -1558,8 +1575,8 @@ bool ImGui::TableNextCell() TableNextRow(); } - ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; - return column->IsActive; + int column_n = table->CurrentColumn; + return (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) != 0; } const char* ImGui::TableGetColumnName(int column_n) @@ -1581,7 +1598,7 @@ bool ImGui::TableGetColumnIsVisible(int column_n) return false; if (column_n < 0) column_n = table->CurrentColumn; - return (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) != 0; + return (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) != 0; } int ImGui::TableGetColumnIndex() @@ -1608,7 +1625,7 @@ bool ImGui::TableSetColumnIndex(int column_idx) TableBeginCell(table, column_idx); } - return (table->ActiveMaskByIndex & ((ImU64)1 << column_idx)) != 0; + return (table->VisibleMaskByIndex & ((ImU64)1 << column_idx)) != 0; } ImRect ImGui::TableGetCellRect() @@ -1627,6 +1644,15 @@ const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_no) return &table->ColumnsNames.Buf[column->NameOffset]; } +void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_no) +{ + // Disable clipping then auto-fit, will take 2 frames + // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) + ImGuiTableColumn* column = &table->Columns[column_no]; + column->CannotSkipItemsQueue = (1 << 0); + column->AutoFitQueue = (1 << 1); +} + void ImGui::PushTableBackground() { ImGuiContext& g = *GImGui; @@ -1664,7 +1690,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) { const bool can_resize = !(selected_column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && selected_column->IsActive; if (MenuItem("Size column to fit", NULL, false, can_resize)) - selected_column->AutoFitFrames = 1; + TableSetColumnAutofit(table, selected_column_n); } if (MenuItem("Size all columns to fit", NULL)) @@ -1673,7 +1699,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) { ImGuiTableColumn* column = &table->Columns[column_n]; if (column->IsActive) - column->AutoFitFrames = 1; + TableSetColumnAutofit(table, column_n); } } want_separator = true; @@ -2280,6 +2306,7 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan // - DebugNodeTable() [Internal] //------------------------------------------------------------------------- +#ifndef IMGUI_DISABLE_METRICS_WINDOW void ImGui::DebugNodeTable(ImGuiTable* table) { char buf[256]; @@ -2296,11 +2323,11 @@ void ImGui::DebugNodeTable(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[n]; const char* name = TableGetColumnName(table, n); BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" - "Active: %d, DrawChannels: %d,%d\n" + "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", n, column->IndexDisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, - column->IsActive, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, + column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->WidthGiven, column->WidthRequested, column->ResizeWeight, column->UserID, column->Flags, (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", @@ -2324,6 +2351,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) TreePop(); } } +#endif // #ifndef IMGUI_DISABLE_METRICS_WINDOW //------------------------------------------------------------------------- From 0c3d7bb1542f7aa06d19fac82d88f1a72dc848a6 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 28 Dec 2019 19:34:19 +0100 Subject: [PATCH 008/144] Tables: Double-clicking on fixed column to resize. Extracted code BeginTableEx(). # Conflicts: # imgui_internal.h --- imgui_internal.h | 2 ++ imgui_tables.cpp | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index f5678f83..ea9b552b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2181,6 +2181,8 @@ namespace ImGui //IMGUI_API int GetTableColumnNo(); //IMGUI_API bool SetTableColumnNo(int column_n); //IMGUI_API int GetTableLineNo(); + + IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void TableBeginInitVisibility(ImGuiTable* table); IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e847d477..4edcaad0 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -145,13 +145,18 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) // FIXME-TABLE: Replace enlarge vs fixed width by a flag. // Even if not really useful, we allow 'inner_scroll_width < outer_size.x' for consistency and to facilitate understanding of what the value does. bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) +{ + ImGuiID id = GetID(str_id); + return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width); +} + +bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) { ImGuiContext& g = *GImGui; ImGuiWindow* outer_window = GetCurrentWindow(); IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS && "Only 0..63 columns allowed!"); if (flags & ImGuiTableFlags_ScrollX) IM_ASSERT(inner_width >= 0.0f); - ImGuiID id = GetID(str_id); const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; const ImVec2 avail_size = GetContentRegionAvail(); @@ -209,7 +214,7 @@ bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags // Create scrolling region (without border = zero window padding) ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; - BeginChildEx(str_id, instance_id, table->OuterRect.GetSize(), false, child_flags); + BeginChildEx(name, instance_id, table->OuterRect.GetSize(), false, child_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); @@ -810,7 +815,14 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) KeepAliveID(column_id); bool hovered = false, held = false; - ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if (pressed && IsMouseDoubleClicked(0) && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + { + // FIXME-TABLE: Double-clicking on column edge could auto-fit weighted column? + TableSetColumnAutofit(table, column_n); + ClearActiveID(); + held = hovered = false; + } if (held) { table->ResizedColumn = (ImS8)column_n; From 81453ac42cd76aabe2664cd6394e2f335368d17b Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 29 Dec 2019 17:10:42 +0100 Subject: [PATCH 009/144] Tables: Comments, better assert, moved some internal flags out of the way. --- imgui.h | 50 +++++++++++++++++++++++++++--------------------- imgui_internal.h | 2 +- imgui_tables.cpp | 21 +++++++++++++------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/imgui.h b/imgui.h index 69ef0d42..a67a541a 100644 --- a/imgui.h +++ b/imgui.h @@ -671,6 +671,8 @@ namespace ImGui // - In most situations you can use TableNextRow() + TableSetColumnIndex() to populate a table. // - If you are using tables as a sort of grid, populating every columns with the same type of contents, // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). + // - See Demo->Tables for details. + // - See ImGuiTableFlags_ enums for a description of available flags. #define IMGUI_HAS_TABLE 1 IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! @@ -678,14 +680,14 @@ namespace ImGui IMGUI_API bool TableNextCell(); // append into the next column (next column, or next row if currently in last column). Return true if column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. IMGUI_API int TableGetColumnIndex(); // return current column index. - IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Use pass -1 to use current column. - IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Use pass -1 to use current column. - IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Use pass -1 to use current column. + IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. // Tables: Headers & Columns declaration - // - Use TableSetupColumn() to specify resizing policy, default width, name, id, specific flags etc. + // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu // - Use TableAutoHeaders() to submit the whole header row, otherwise you may treat the header row as a regular row, manually call TableHeader() and other widgets. - // - Headers are required to perform some interactions: reordering, sorting, context menu // FIXME-TABLES: remove context from this list! + // - Headers are required to perform some interactions: reordering, sorting, context menu // FIXME-TABLE: remove context from this list! IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableHeader(const char* label); // submit one header cell manually. @@ -1005,6 +1007,10 @@ enum ImGuiTabItemFlags_ }; // Flags for ImGui::BeginTable() +// - Columns can either varying resizing policy: "Fixed", "Stretch" or "AlwaysAutoResize". Toggling ScrollX needs to alter default sizing policy. +// - Sizing policy have many subtle side effects which may be hard to fully comprehend at first.. They'll eventually make sense. +// - with SizingPolicyFixedX (default is ScrollX is on): Columns can be enlarged as needed. Enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rect. Only Fixed columns allowed. Weighted columns will calculate their width assuming no scrolling. +// - with SizingPolicyStretchX (default is ScrollX is off): Fit all columns within available table width (so it doesn't make sense to use ScrollX with Stretch columns!). Fixed and Weighted columns allowed. enum ImGuiTableFlags_ { // Features @@ -1023,26 +1029,26 @@ enum ImGuiTableFlags_ ImGuiTableFlags_BordersFullHeight = 1 << 10, // Borders covers all lines even when Headers are being used, allow resizing all rows. ImGuiTableFlags_Borders = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersH, // Padding, Sizing - ImGuiTableFlags_NoClipX = 1 << 12, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items with be able to overflow) - ImGuiTableFlags_SizingPolicyStretchX = 1 << 13, // (Default if ScrollX is off) Columns will default to use ImGuiTableColumnFlags_WidthStretch. Fit all columns within available width. Fixed and Weighted columns allowed. - ImGuiTableFlags_SizingPolicyFixedX = 1 << 14, // (Default if ScrollX is on) Columns will default to use ImGuiTableColumnFlags_WidthFixed or WidthAuto. Enlarge as needed: enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rect. Only Fixed columns allowed. Weighted columns will calculate their width assuming no scrolling. - ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribute to automatic width calculation for every columns. + ImGuiTableFlags_NoClipX = 1 << 12, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow) + ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use WidthFixed or WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use WidthStretch policy. Read description above for more details. + ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) // Scrolling ImGuiTableFlags_ScrollX = 1 << 17, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. ImGuiTableFlags_ScrollY = 1 << 18, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, - ImGuiTableFlags_ScrollFreezeRowsShift_ = 19, // We can lock 1 to 3 rows (starting from the top). Encode each of those values as dedicated flags. - ImGuiTableFlags_ScrollFreezeTopRow = 1 << ImGuiTableFlags_ScrollFreezeRowsShift_, - ImGuiTableFlags_ScrollFreeze2Rows = 2 << ImGuiTableFlags_ScrollFreezeRowsShift_, - ImGuiTableFlags_ScrollFreeze3Rows = 3 << ImGuiTableFlags_ScrollFreezeRowsShift_, - ImGuiTableFlags_ScrollFreezeColumnsShift_ = 21, // We can lock 1 to 3 columns (starting from the left). Encode each of those values as dedicated flags. - ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << ImGuiTableFlags_ScrollFreezeColumnsShift_, - ImGuiTableFlags_ScrollFreeze2Columns = 2 << ImGuiTableFlags_ScrollFreezeColumnsShift_, - ImGuiTableFlags_ScrollFreeze3Columns = 3 << ImGuiTableFlags_ScrollFreezeColumnsShift_, + ImGuiTableFlags_ScrollFreezeTopRow = 1 << 19, // We can lock 1 to 3 rows (starting from the top). Use with ScrollY enabled. + ImGuiTableFlags_ScrollFreeze2Rows = 2 << 19, + ImGuiTableFlags_ScrollFreeze3Rows = 3 << 19, + ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << 21, // We can lock 1 to 3 columns (starting from the left). Use with ScrollX enabled. + ImGuiTableFlags_ScrollFreeze2Columns = 2 << 21, + ImGuiTableFlags_ScrollFreeze3Columns = 3 << 21, - // Combinations and masks + // [Internal] Combinations and masks ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX, + ImGuiTableFlags_ScrollFreezeRowsShift_ = 19, + ImGuiTableFlags_ScrollFreezeColumnsShift_ = 21, ImGuiTableFlags_ScrollFreezeRowsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeRowsShift_, ImGuiTableFlags_ScrollFreezeColumnsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeColumnsShift_ }; @@ -1066,13 +1072,13 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_NoHeaderWidth = 1 << 11, // Header width don't contribute to automatic column width. ImGuiTableColumnFlags_PreferSortAscending = 1 << 12, // Make the initial sort direction Ascending when first sorting on this column (default). ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. + + // [Internal] Combinations and masks + ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, + ImGuiTableColumnFlags_NoDirectResize_ = 1 << 20 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) //ImGuiTableColumnFlags_AlignLeft = 1 << 14, //ImGuiTableColumnFlags_AlignCenter = 1 << 15, //ImGuiTableColumnFlags_AlignRight = 1 << 16, - - // Combinations and masks - ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, - ImGuiTableColumnFlags_NoDirectResize_ = 1 << 20 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) //ImGuiTableColumnFlags_AlignMask_ = ImGuiTableColumnFlags_AlignLeft | ImGuiTableColumnFlags_AlignCenter | ImGuiTableColumnFlags_AlignRight }; diff --git a/imgui_internal.h b/imgui_internal.h index ea9b552b..7f2dc1d9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1895,7 +1895,7 @@ struct ImGuiTable int CurrentColumn; int CurrentRow; ImS16 InstanceNo; // Count of BeginTable() calls with same ID in the same frame (generally 0) - ImS16 InstanceInteracted; + ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with float RowPosY1; float RowPosY2; float RowTextBaseline; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 4edcaad0..79ec8ef7 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -106,6 +106,7 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) flags |= ImGuiTableFlags_BordersV; // Adjust flags: disable top rows freezing if there's no scrolling + // In theory we could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. if ((flags & ImGuiTableFlags_ScrollX) == 0) flags &= ~ImGuiTableFlags_ScrollFreezeColumnsMask_; if ((flags & ImGuiTableFlags_ScrollY) == 0) @@ -154,6 +155,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG { ImGuiContext& g = *GImGui; ImGuiWindow* outer_window = GetCurrentWindow(); + + // Sanity checks IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS && "Only 0..63 columns allowed!"); if (flags & ImGuiTableFlags_ScrollX) IM_ASSERT(inner_width >= 0.0f); @@ -840,7 +843,7 @@ void ImGui::EndTable() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); + IM_ASSERT(table != NULL && "Only call EndTable() is BeginTable() returns true!"); const ImGuiTableFlags flags = table->Flags; ImGuiWindow* inner_window = table->InnerWindow; @@ -1292,8 +1295,8 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Can only call TableSetupColumn() after BeginTable()!"); - IM_ASSERT(!table->IsLayoutLocked && "Can only call TableSetupColumn() before first row!"); + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(!table->IsLayoutLocked && "Need to call call TableSetupColumn() before first row!"); IM_ASSERT(table->DeclColumnsCount >= 0 && table->DeclColumnsCount < table->ColumnsCount && "Called TableSetupColumn() too many times!"); ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; @@ -1505,7 +1508,8 @@ void ImGui::TableEndRow(ImGuiTable* table) table->IsInsideRow = false; } -// [Internal] This is called a lot, so we need to be mindful of unnecessary overhead! +// [Internal] Called by TableNextRow()TableNextCell()! +// This is called a lot, so we need to be mindful of unnecessary overhead. void ImGui::TableBeginCell(ImGuiTable* table, int column_no) { table->CurrentColumn = column_no; @@ -1550,7 +1554,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) } } -// [Internal] +// [Internal] Called by TableNextRow()TableNextCell()! void ImGui::TableEndCell(ImGuiTable* table) { ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; @@ -1684,6 +1688,7 @@ void ImGui::PopTableBackground() PopClipRect(); } +// Output context menu into current window (generally a popup) // FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) { @@ -1751,7 +1756,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) } } -// This is a helper to output headers based on the column names declared in TableSetupColumn() +// This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). // The intent is that advanced users would not need to use this helper and may create their own. void ImGui::TableAutoHeaders() { @@ -1761,7 +1766,8 @@ void ImGui::TableAutoHeaders() return; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table && table->CurrentRow == -1); + IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); + IM_ASSERT(table->CurrentRow == -1); int open_context_popup = INT_MAX; @@ -1852,6 +1858,7 @@ void ImGui::TableHeader(const char* label) return; ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; From 046fad01f1185d34c203d4f58f5adc0b7d28eaa9 Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 29 Dec 2019 17:12:09 +0100 Subject: [PATCH 010/144] Tables: Return false when window is Collapsed (consistent + helpful for doc) + Fix empty context menu. --- imgui_tables.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 79ec8ef7..2ea3a96b 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -155,6 +155,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG { ImGuiContext& g = *GImGui; ImGuiWindow* outer_window = GetCurrentWindow(); + if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible. + return false; // Sanity checks IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS && "Only 0..63 columns allowed!"); @@ -1839,12 +1841,13 @@ void ImGui::TableAutoHeaders() // Context Menu if (open_context_popup != INT_MAX) - { - table->IsContextPopupOpen = true; - table->ContextPopupColumn = (ImS8)open_context_popup; - table->InstanceInteracted = table->InstanceNo; - OpenPopup("##TableContextMenu"); - } + if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + table->IsContextPopupOpen = true; + table->ContextPopupColumn = (ImS8)open_context_popup; + table->InstanceInteracted = table->InstanceNo; + OpenPopup("##TableContextMenu"); + } } // Emit a column header (text + optional sort order) From 78b12068d9d73ec91859fe52a7fad7b971b37f49 Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 29 Dec 2019 17:35:12 +0100 Subject: [PATCH 011/144] Tables: Disable initial output prior to NextRow call to avoid misleading users. Fixed some inconsistency with BeginTable/EndTable without row. Move some of the TableBegin() code in TableBeginUpdateColumns(). Allow to submit multiple header lines. --- imgui_internal.h | 2 +- imgui_tables.cpp | 88 ++++++++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 7f2dc1d9..ee6977dc 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2183,7 +2183,7 @@ namespace ImGui //IMGUI_API int GetTableLineNo(); IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); - IMGUI_API void TableBeginInitVisibility(ImGuiTable* table); + IMGUI_API void TableBeginUpdateColumns(ImGuiTable* table); IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2ea3a96b..3d04fae8 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -64,7 +64,7 @@ // Typical call flow: (root level is public API): // - BeginTable() user begin into a table // - BeginChild() - (if ScrollX/ScrollY is set) -// - TableBeginInitVisibility() - lock columns visibility +// - TableBeginUpdateColumns() - apply resize/order requests, lock columns active state, order // - TableSetupColumn() user submit columns details (optional) // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) // - TableSortSpecsClickColumn() @@ -306,8 +306,26 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsFirstFrame || table->IsSettingsRequestLoad) TableLoadSettings(table); + // Grab a copy of window fields we will modify + table->BackupSkipItems = inner_window->SkipItems; + table->BackupWorkRect = inner_window->WorkRect; + table->BackupCursorMaxPos = inner_window->DC.CursorMaxPos; + + // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. + // This is not strictly necessary but will reduce cases were misleading "out of table" output will be confusing to the user. + // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. + inner_window->SkipItems = true; + + // Update/lock which columns will be Active for the frame + TableBeginUpdateColumns(table); + + return true; +} + +void ImGui::TableBeginUpdateColumns(ImGuiTable* table) +{ // Handle resizing request - // (We process this at the first beginning of the frame) + // (We process this at the first TableBegin of the frame) // FIXME-TABLE: Preserve contents width _while resizing down_ until releasing. // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling. if (table->InstanceNo == 0) @@ -341,29 +359,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Handle display order reset request if (table->IsResetDisplayOrderRequest) { - for (int n = 0; n < columns_count; n++) + for (int n = 0; n < table->ColumnsCount; n++) table->DisplayOrder[n] = table->Columns[n].IndexDisplayOrder = (ImU8)n; table->IsResetDisplayOrderRequest = false; table->IsSettingsDirty = true; } - TableBeginInitVisibility(table); - - // Grab a copy of window fields we will modify - table->BackupSkipItems = inner_window->SkipItems; - table->BackupWorkRect = inner_window->WorkRect; - table->BackupCursorMaxPos = inner_window->DC.CursorMaxPos; - - if (flags & ImGuiTableFlags_NoClipX) - table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 1); - else - inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); - - return true; -} - -void ImGui::TableBeginInitVisibility(ImGuiTable* table) -{ // Setup and lock Active state and order table->ColumnsActiveCount = 0; table->IsDefaultDisplayOrder = true; @@ -706,7 +707,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Max.x = column->MaxX;// -1.0f; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); - + column->IsClipped = (column->ClipRect.Max.x <= column->ClipRect.Min.x) && (column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0; if (column->IsClipped) { @@ -776,6 +777,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsContextPopupOpen = false; } } + + // Initial state + ImGuiWindow* inner_window = table->InnerWindow; + if (table->Flags & ImGuiTableFlags_NoClipX) + table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 1); + else + inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); } // Process interaction on resizing borders. Actual size change will be applied in EndTable() @@ -847,6 +855,15 @@ void ImGui::EndTable() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Only call EndTable() is BeginTable() returns true!"); + // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some cases, + // and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) + //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); + + // If the user never got to call TableNextRow() or TableNextCell(), we call layout ourselves to ensure all our + // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + const ImGuiTableFlags flags = table->Flags; ImGuiWindow* inner_window = table->InnerWindow; ImGuiWindow* outer_window = table->OuterWindow; @@ -1664,7 +1681,7 @@ const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_no) void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_no) { - // Disable clipping then auto-fit, will take 2 frames + // Disable clipping then auto-fit, will take 2 frames // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) ImGuiTableColumn* column = &table->Columns[column_no]; column->CannotSkipItemsQueue = (1 << 0); @@ -1759,23 +1776,23 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) } // This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). -// The intent is that advanced users would not need to use this helper and may create their own. +// The intent is that advanced users willing to create customized headers would not need to use this helper and may create their own. +// However presently this function uses too many internal structures/calls. void ImGui::TableAutoHeaders() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - if (window->SkipItems) - return; ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); - IM_ASSERT(table->CurrentRow == -1); - int open_context_popup = INT_MAX; + TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight()); + if (window->SkipItems) + return; // This for loop is constructed to not make use of internal functions, // as this is intended to be a base template to copy and build from. - TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight()); + int open_context_popup = INT_MAX; const int columns_count = table->ColumnsCount; for (int column_n = 0; column_n < columns_count; column_n++) { @@ -1788,7 +1805,7 @@ void ImGui::TableAutoHeaders() #if 0 if (column_n < 2) { - static bool b[10] = {}; + static bool b[2] = {}; PushID(column_n); PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); Checkbox("##", &b[column_n]); @@ -1801,7 +1818,8 @@ void ImGui::TableAutoHeaders() // [DEBUG] //if (g.IO.KeyCtrl) { static char buf[32]; name = buf; ImGuiTableColumn* c = &table->Columns[column_n]; if (c->Flags & ImGuiTableColumnFlags_WidthStretch) ImFormatString(buf, 32, "%.3f>%.1f", c->ResizeWeight, c->WidthGiven); else ImFormatString(buf, 32, "%.1f", c->WidthGiven); } - PushID(table->InstanceNo * table->ColumnsCount + column_n); // Allow unnamed labels (generally accidental, but let's behave nicely with them) + // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) + PushID(table->InstanceNo * table->ColumnsCount + column_n); TableHeader(name); PopID(); @@ -1811,15 +1829,19 @@ void ImGui::TableAutoHeaders() } // FIXME-TABLE: This is not user-land code any more... + // FIXME-TABLE: Need to explain why this is here! window->SkipItems = table->BackupSkipItems; // Allow opening popup from the right-most section after the last column // FIXME-TABLE: This is not user-land code any more... perhaps instead we should expose hovered column. // and allow some sort of row-centric IsItemHovered() for full flexibility? - const float unused_x1 = (table->RightMostActiveColumn != -1) ? table->Columns[table->RightMostActiveColumn].MaxX : table->WorkRect.Min.x; + float unused_x1 = table->WorkRect.Min.x; + if (table->RightMostActiveColumn != -1) + unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostActiveColumn].MaxX); if (unused_x1 < table->WorkRect.Max.x) { - // FIXME: We inherit ClipRect/SkipItem from last submitted column (active or not), let's override + // FIXME: We inherit ClipRect/SkipItem from last submitted column (active or not), let's temporarily override it. + // Because we don't perform any rendering here we just overwrite window->ClipRect used by logic. window->ClipRect = table->InnerClipRect; ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; @@ -1839,7 +1861,7 @@ void ImGui::TableAutoHeaders() window->ClipRect = window->DrawList->_ClipRectStack.back(); } - // Context Menu + // Open Context Menu if (open_context_popup != INT_MAX) if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { From 47b39f6371573dd181bef97d96ff9cf7a15c8cbe Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 29 Dec 2019 19:48:10 +0100 Subject: [PATCH 012/144] Tables: Demo: Moved Columns section into Tables & Columns section under a Legacy section. --- imgui_demo.cpp | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7c0c4a7f..b9278f46 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -484,7 +484,6 @@ void ImGui::ShowDemoWindow(bool* p_open) ShowDemoWindowLayout(); ShowDemoWindowPopups(); ShowDemoWindowTables(); - ShowDemoWindowColumns(); ShowDemoWindowMisc(); // End of ShowDemoWindow() @@ -3289,7 +3288,7 @@ const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; static void ShowDemoWindowTables() { //ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (!ImGui::CollapsingHeader("Tables")) + if (!ImGui::CollapsingHeader("Tables & Columns")) return; ImGui::PushID("Tables"); @@ -4175,27 +4174,23 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + ImGui::PopID(); + + ShowDemoWindowColumns(); + if (disable_indent) ImGui::PopStyleVar(); - ImGui::PopID(); } -// 2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful Tables API! +// Demonstrate old/legacy Columns API! +// [2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful BeginTable() API!] static void ShowDemoWindowColumns() { - if (!ImGui::CollapsingHeader("Columns")) - return; - - ImGui::PushID("Columns"); - - static bool disable_indent = false; - ImGui::Checkbox("Disable tree indentation", &disable_indent); + bool open = ImGui::TreeNode("Legacy Columns API"); ImGui::SameLine(); - HelpMarker("Disable the indenting of tree nodes so demo columns can use the full window width."); - if (disable_indent) - ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f); - - ImGui::TextWrapped("Note: Columns are under-featured and not maintained. Prefer using the more flexible and powerful Tables API!"); + HelpMarker("Columns() is an old API! Prefer using the more flexible and powerful BeginTable() API!"); + if (!open) + return; // Basic columns if (ImGui::TreeNode("Basic")) @@ -4407,9 +4402,7 @@ static void ShowDemoWindowColumns() ImGui::TreePop(); } - if (disable_indent) - ImGui::PopStyleVar(); - ImGui::PopID(); + ImGui::TreePop(); } static void ShowDemoWindowMisc() From 1db8d421cf650e21d0e01bcea5350033816da758 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 30 Dec 2019 13:01:36 +0100 Subject: [PATCH 013/144] Tables: Fix scroll when releasing resize for multi-instances. Comments. Renaming. --- imgui_internal.h | 12 ++++++------ imgui_tables.cpp | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index ee6977dc..fdf19577 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1932,8 +1932,8 @@ struct ImGuiTable ImS8 HoveredColumnBody; // [DEBUG] Unlike HoveredColumnBorder this doesn't fulfill all Hovering rules properly. Used for debugging/tools for now. ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). ImS8 ResizedColumn; // Index of column being resized. Reset by InstanceNo==0. - ImS8 HeadHeaderColumn; // Index of column header being held. - ImS8 LastResizedColumn; + ImS8 LastResizedColumn; // Index of column being resized from previous frame. + ImS8 HeldHeaderColumn; // Index of column header being held. ImS8 ReorderColumn; // Index of column being reordered. (not cleared) ImS8 ReorderColumnDir; // -1 or +1 ImS8 RightMostActiveColumn; // Index of right-most non-hidden column. @@ -1945,11 +1945,11 @@ struct ImGuiTable ImS8 FreezeColumnsRequest; // Requested frozen columns count ImS8 FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. - bool IsInsideRow; // Set if inside TableBeginRow()/TableEndRow(). - bool IsFirstFrame; + bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). + bool IsInitializing; bool IsSortSpecsDirty; - bool IsUsingHeaders; // Set if the first row had the ImGuiTableRowFlags_Headers flag. - bool IsContextPopupOpen; + bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. + bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). bool IsSettingsRequestLoad; bool IsSettingsLoaded; bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3d04fae8..f4dde061 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -61,6 +61,7 @@ // [SECTION] Widgets: BeginTable, EndTable, etc. //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // Typical call flow: (root level is public API): // - BeginTable() user begin into a table // - BeginChild() - (if ScrollX/ScrollY is set) @@ -81,6 +82,7 @@ // - TableSetColumnWidth() - apply resizing width // - TableUpdateColumnsWeightFromWidth() // - EndChild() - (if ScrollX/ScrollY is set) +//----------------------------------------------------------------------------- // Configuration static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. @@ -190,12 +192,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Initialize table->ID = id; table->Flags = flags; - table->IsFirstFrame = (table->LastFrameActive == -1); table->InstanceNo = (ImS16)instance_no; table->LastFrameActive = g.FrameCount; table->OuterWindow = table->InnerWindow = outer_window; table->ColumnsCount = columns_count; table->ColumnsNames.Buf.resize(0); + table->IsInitializing = false; table->IsLayoutLocked = false; table->InnerWidth = inner_width; table->OuterRect = outer_rect; @@ -256,11 +258,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->FreezeColumnsCount = (inner_window->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); table->DeclColumnsCount = 0; - table->LastResizedColumn = table->ResizedColumn; table->HoveredColumnBody = -1; table->HoveredColumnBorder = -1; table->RightMostActiveColumn = -1; - table->IsFirstFrame = false; // FIXME-TABLE FIXME-STYLE: Using opaque colors facilitate overlapping elements of the grid //table->BorderOuterColor = GetColorU32(ImGuiCol_Separator, 1.00f); @@ -289,8 +289,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Setup default columns state if (table->Columns.Size == 0) { - table->IsFirstFrame = true; - table->IsSortSpecsDirty = true; + table->IsInitializing = table->IsSettingsRequestLoad = table->IsSortSpecsDirty = true; table->Columns.reserve(columns_count); table->DisplayOrder.reserve(columns_count); for (int n = 0; n < columns_count; n++) @@ -303,7 +302,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } // Load settings - if (table->IsFirstFrame || table->IsSettingsRequestLoad) + if (table->IsSettingsRequestLoad) TableLoadSettings(table); // Grab a copy of window fields we will modify @@ -332,6 +331,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) { if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) TableSetColumnWidth(table, &table->Columns[table->ResizedColumn], table->ResizedColumnNextWidth); + table->LastResizedColumn = table->ResizedColumn; table->ResizedColumnNextWidth = FLT_MAX; table->ResizedColumn = -1; } @@ -340,9 +340,9 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) // Note: we don't clear ReorderColumn after handling the request. if (table->InstanceNo == 0) { - if (table->HeadHeaderColumn == -1 && table->ReorderColumn != -1) + if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) table->ReorderColumn = -1; - table->HeadHeaderColumn = -1; + table->HeldHeaderColumn = -1; if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) { IM_ASSERT(table->ReorderColumnDir == -1 || table->ReorderColumnDir == +1); @@ -554,10 +554,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) width_request = ImMax(width_request, (float)column->ContentWidthHeadersDesired); column->WidthRequested = ImMax(width_request + padding_auto_x, min_column_width); - // FIXME-TABLE: Increase minimum size during init frame so avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. + // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. // Otherwise what tends to happen is that TextWrapped would output a very large height (= first frame scrollbar display very off + clipper would skip lots of items) // This is merely making the side-effect less extreme, but doesn't properly fixes it. - if (column->AutoFitQueue > 0x01 && table->IsFirstFrame) + if (column->AutoFitQueue > 0x01 && table->IsInitializing) column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); } width_fixed += column->WidthRequested; @@ -929,7 +929,7 @@ void ImGui::EndTable() { inner_window->Scroll.x = 0.0f; } - else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX) + else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceNo) { ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn]; if (column->MaxX < table->InnerClipRect.Min.x) @@ -1333,7 +1333,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flags = column->Flags; // Initialize defaults - if (table->IsFirstFrame && !table->IsSettingsLoaded) + if (table->IsInitializing && !table->IsSettingsLoaded) { // Init width or weight // Disable auto-fit if a default fixed width has been specified @@ -1913,7 +1913,7 @@ void ImGui::TableHeader(const char* label) const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, row_height)); const bool held = IsItemActive(); if (held) - table->HeadHeaderColumn = (ImS8)column_n; + table->HeldHeaderColumn = (ImS8)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; // Drag and drop: re-order columns. Frozen columns are not reorderable. @@ -2123,7 +2123,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) } // Fallback default sort order (if no column has the ImGuiTableColumnFlags_DefaultSort flag) - if (sort_order_count == 0 && table->IsFirstFrame) + if (sort_order_count == 0 && table->IsInitializing) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; From 17578e215a4b04f1d8ca725d099dcaf5b0ff59ae Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 30 Dec 2019 15:14:15 +0100 Subject: [PATCH 014/144] Tables: Separating inner/outer borders flags per axis so it is possible to remove outer vertical borders to mimic old columns. VInner or VOuter only don't have correct padding/spacing. --- imgui.h | 15 +++++--- imgui_demo.cpp | 43 +++++++++++++++------- imgui_internal.h | 4 +-- imgui_tables.cpp | 93 ++++++++++++++++++++++++++++++------------------ 4 files changed, 101 insertions(+), 54 deletions(-) diff --git a/imgui.h b/imgui.h index a67a541a..c9920866 100644 --- a/imgui.h +++ b/imgui.h @@ -1023,11 +1023,16 @@ enum ImGuiTableFlags_ ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. // Decoration ImGuiTableFlags_RowBg = 1 << 6, // Use ImGuiCol_TableRowBg and ImGuiCol_TableRowBgAlt colors behind each rows. - ImGuiTableFlags_BordersOuter = 1 << 7, // Draw outer borders. - ImGuiTableFlags_BordersV = 1 << 8, // Draw vertical borders between columns. - ImGuiTableFlags_BordersH = 1 << 9, // Draw horizontal borders between rows. - ImGuiTableFlags_BordersFullHeight = 1 << 10, // Borders covers all lines even when Headers are being used, allow resizing all rows. - ImGuiTableFlags_Borders = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersH, + ImGuiTableFlags_BordersHInner = 1 << 7, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersHOuter = 1 << 8, // Draw horizontal borders at the top and bottom. + ImGuiTableFlags_BordersVInner = 1 << 9, // Draw vertical borders between columns. + ImGuiTableFlags_BordersVOuter = 1 << 10, // Draw vertical borders on the left and right sides. + ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersHInner | ImGuiTableFlags_BordersHOuter, // Draw horizontal borders. + ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersVInner | ImGuiTableFlags_BordersVOuter, // Draw vertical borders. + ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersVInner | ImGuiTableFlags_BordersHInner, // Draw inner borders. + ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersVOuter | ImGuiTableFlags_BordersHOuter, // Draw outer borders. + ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. + ImGuiTableFlags_BordersVFullHeight = 1 << 11, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. // Padding, Sizing ImGuiTableFlags_NoClipX = 1 << 12, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow) ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use WidthFixed or WidthAlwaysAutoResize policy. Read description above for more details. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b9278f46..d4c8f643 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3379,17 +3379,31 @@ static void ShowDemoWindowTables() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); - if (ImGui::TreeNode("With borders, background")) + if (ImGui::TreeNode("Borders, background")) { // Expose a few Borders related flags interactively static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; static bool display_width = false; ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&flags, ImGuiTableFlags_Borders); - ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersOuter\n | ImGuiTableFlags_BordersV\n | ImGuiTableFlags_BordersH"); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersVInner\n | ImGuiTableFlags_BordersVOuter\n | ImGuiTableFlags_BordersHInner\n | ImGuiTableFlags_BordersHOuter"); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersHOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersHOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); + ImGui::Unindent(); + + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersVOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVInner", (unsigned int*)&flags, ImGuiTableFlags_BordersVInner); + ImGui::Unindent(); + + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", (unsigned int*)&flags, ImGuiTableFlags_BordersInner); + ImGui::Unindent(); ImGui::Checkbox("Debug Display width", &display_width); if (ImGui::BeginTable("##table1", 3, flags)) @@ -3696,7 +3710,7 @@ static void ShowDemoWindowTables() { HelpMarker("This demonstrate embedding a table into another table cell."); - if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersVFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("A0"); ImGui::TableSetupColumn("A1"); @@ -3705,7 +3719,7 @@ static void ShowDemoWindowTables() ImGui::TableNextRow(); ImGui::Text("A0 Cell 0"); { float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; - if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersVFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("B0"); ImGui::TableSetupColumn("B1"); @@ -3737,13 +3751,15 @@ static void ShowDemoWindowTables() { HelpMarker("This section allows you to interact and see the effect of StretchX vs FixedX sizing policies depending on whether Scroll is enabled and the contents of your columns."); enum ContentsType { CT_ShortText, CT_LongText, CT_Button, CT_StretchButton, CT_InputText }; - static int contents_type = CT_ShortText; + static int contents_type = CT_StretchButton; ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12); ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Stretch Button\0InputText\0"); static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; - ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersHOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersHOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVInner", (unsigned int*)&flags, ImGuiTableFlags_BordersVInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersVOuter); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) @@ -3952,10 +3968,13 @@ static void ShowDemoWindowTables() ImGui::BulletText("Decoration:"); ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersFullHeight", (unsigned int*)&flags, ImGuiTableFlags_BordersFullHeight); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersVOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVInner", (unsigned int*)&flags, ImGuiTableFlags_BordersVInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersHOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersHOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersVFullHeight", (unsigned int*)&flags, ImGuiTableFlags_BordersVFullHeight); ImGui::Unindent(); ImGui::BulletText("Padding, Sizing:"); diff --git a/imgui_internal.h b/imgui_internal.h index fdf19577..f897815c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1903,8 +1903,8 @@ struct ImGuiTable ImGuiTableRowFlags LastRowFlags : 16; int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper) ImU32 RowBgColor; // Request for current row background color - ImU32 BorderOuterColor; - ImU32 BorderInnerColor; + ImU32 BorderColorStrong; + ImU32 BorderColorLight; float BorderX1; float BorderX2; float CellPaddingX1; // Padding from each borders diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f4dde061..f36eccd5 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -105,7 +105,7 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) // Adjust flags: enforce borders when resizable if (flags & ImGuiTableFlags_Resizable) - flags |= ImGuiTableFlags_BordersV; + flags |= ImGuiTableFlags_BordersVInner; // Adjust flags: disable top rows freezing if there's no scrolling // In theory we could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. @@ -232,18 +232,23 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG PushID(instance_id); } - const bool has_cell_padding_x = (flags & (ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV)) != 0; - ImGuiWindow* inner_window = table->InnerWindow; - table->CurrentColumn = -1; - table->CurrentRow = -1; - table->RowBgColorCounter = 0; - table->LastRowFlags = ImGuiTableRowFlags_None; + // Borders + // - None ........Content..... Pad .....Content........ + // - VOuter | Pad ..Content..... Pad .....Content.. Pad | // FIXME-TABLE: Not handled properly + // - VInner ........Content.. Pad | Pad ..Content........ // FIXME-TABLE: Not handled properly + // - VOuter+VInner | Pad ..Content.. Pad | Pad ..Content.. Pad | + const bool has_cell_padding_x = (flags & ImGuiTableFlags_BordersVOuter) != 0; + ImGuiWindow* inner_window = table->InnerWindow; table->CellPaddingX1 = has_cell_padding_x ? g.Style.CellPadding.x + 1.0f : 0.0f; table->CellPaddingX2 = has_cell_padding_x ? g.Style.CellPadding.x : 0.0f; table->CellPaddingY = g.Style.CellPadding.y; table->CellSpacingX = has_cell_padding_x ? 0.0f : g.Style.CellPadding.x; + table->CurrentColumn = -1; + table->CurrentRow = -1; + table->RowBgColorCounter = 0; + table->LastRowFlags = ImGuiTableRowFlags_None; table->HostClipRect = inner_window->ClipRect; table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width @@ -265,8 +270,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // FIXME-TABLE FIXME-STYLE: Using opaque colors facilitate overlapping elements of the grid //table->BorderOuterColor = GetColorU32(ImGuiCol_Separator, 1.00f); //table->BorderInnerColor = GetColorU32(ImGuiCol_Separator, 0.60f); - table->BorderOuterColor = GetColorU32(ImVec4(0.31f, 0.31f, 0.35f, 1.00f)); - table->BorderInnerColor = GetColorU32(ImVec4(0.23f, 0.23f, 0.25f, 1.00f)); + table->BorderColorStrong = GetColorU32(ImVec4(0.31f, 0.31f, 0.35f, 1.00f)); + table->BorderColorLight = GetColorU32(ImVec4(0.23f, 0.23f, 0.25f, 1.00f)); //table->BorderOuterColor = IM_COL32(255, 0, 0, 255); //table->BorderInnerColor = IM_COL32(255, 255, 0, 255); table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); @@ -798,7 +803,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) // the final height from last frame. Because this is only affecting _interaction_ with columns, it is not really problematic. // (whereas the actual visual will be displayed in EndTable() and using the current frame height) // Actual columns highlight/render will be performed in EndTable() and not be affected. - const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersFullHeight); + const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersVFullHeight); const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; const float hit_y1 = table->OuterRect.Min.y; const float hit_y2_full = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); @@ -979,6 +984,7 @@ void ImGui::EndTable() g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; } +// FIXME-TABLE: This is a mess, need to redesign how we render borders. void ImGui::TableDrawBorders(ImGuiTable* table) { ImGuiWindow* inner_window = table->InnerWindow; @@ -986,28 +992,29 @@ void ImGui::TableDrawBorders(ImGuiTable* table) table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); if (inner_window->Hidden || !table->HostClipRect.Overlaps(table->InnerClipRect)) return; + ImDrawList* inner_drawlist = inner_window->DrawList; + ImDrawList* outer_drawlist = outer_window->DrawList; // Draw inner border and resizing feedback const float draw_y1 = table->OuterRect.Min.y; float draw_y2_base = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight; float draw_y2_full = table->OuterRect.Max.y; ImU32 border_base_col; - if (!table->IsUsingHeaders || (table->Flags & ImGuiTableFlags_BordersFullHeight)) + if (!table->IsUsingHeaders || (table->Flags & ImGuiTableFlags_BordersVFullHeight)) { draw_y2_base = draw_y2_full; - border_base_col = table->BorderInnerColor; + border_base_col = table->BorderColorLight; } else { - border_base_col = table->BorderOuterColor; + border_base_col = table->BorderColorStrong; } - if (table->Flags & ImGuiTableFlags_BordersV) - { - const bool draw_left_most_border = (table->Flags & ImGuiTableFlags_BordersOuter) == 0; - if (draw_left_most_border) - inner_window->DrawList->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, 1.0f); + if ((table->Flags & ImGuiTableFlags_BordersVOuter) && (table->InnerWindow == table->OuterWindow)) + inner_drawlist->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, 1.0f); + if (table->Flags & ImGuiTableFlags_BordersVInner) + { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) @@ -1017,7 +1024,10 @@ void ImGui::TableDrawBorders(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; const bool is_hovered = (table->HoveredColumnBorder == column_n); const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceNo); - const bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); + const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; + bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); + if (column->NextActiveColumn == -1 && !is_resizable) + draw_right_border = false; if (draw_right_border && column->MaxX > column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. { // Draw in outer window so right-most column won't be clipped @@ -1030,7 +1040,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) float draw_y2 = draw_y2_base; if (is_hovered || is_resized || (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1)) draw_y2 = draw_y2_full; - inner_window->DrawList->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, 1.0f); + inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, 1.0f); } } } @@ -1044,16 +1054,28 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // and the part that's over scrollbars in the outer window..) // Either solution currently won't allow us to use a larger border size: the border would clipped. ImRect outer_border = table->OuterRect; + const ImU32 outer_col = table->BorderColorStrong; if (inner_window != outer_window) outer_border.Expand(1.0f); - outer_window->DrawList->AddRect(outer_border.Min, outer_border.Max, table->BorderOuterColor); // IM_COL32(255, 0, 0, 255)); + if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) + outer_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col); + else if (table->Flags & ImGuiTableFlags_BordersVOuter) + { + outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col); + outer_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col); + } + else if (table->Flags & ImGuiTableFlags_BordersHOuter) + { + outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col); + outer_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col); + } } - else if (table->Flags & ImGuiTableFlags_BordersH) + if ((table->Flags & ImGuiTableFlags_BordersHInner) && table->RowPosY2 < table->OuterRect.Max.y) { - // Draw bottom-most border + // Draw bottom-most row border const float border_y = table->RowPosY2; if (border_y >= table->BackgroundClipRect.Min.y && border_y < table->BackgroundClipRect.Max.y) - inner_window->DrawList->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderOuterColor); + inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight); } } @@ -1451,21 +1473,22 @@ void ImGui::TableEndRow(ImGuiTable* table) else if (table->Flags & ImGuiTableFlags_RowBg) bg_col = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); - // Decide of separating border color + // Decide of top border color ImU32 border_col = 0; if (table->CurrentRow != 0 || table->InnerWindow == table->OuterWindow) { - if (table->Flags & ImGuiTableFlags_BordersH) + if (table->Flags & ImGuiTableFlags_BordersHInner) { - if (table->CurrentRow == 0 && table->InnerWindow == table->OuterWindow) - border_col = table->BorderOuterColor; - else if (!(table->LastRowFlags & ImGuiTableRowFlags_Headers)) - border_col = table->BorderInnerColor; + //if (table->CurrentRow == 0 && table->InnerWindow == table->OuterWindow) + // border_col = table->BorderOuterColor; + //else + if (table->CurrentRow > 0)// && !(table->LastRowFlags & ImGuiTableRowFlags_Headers)) + border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; } else { - if (table->RowFlags & ImGuiTableRowFlags_Headers) - border_col = table->BorderOuterColor; + //if (table->RowFlags & ImGuiTableRowFlags_Headers) + // border_col = table->BorderOuterColor; } } @@ -1491,10 +1514,10 @@ void ImGui::TableEndRow(ImGuiTable* table) const bool unfreeze_rows = (table->CurrentRow + 1 == table->FreezeRowsCount && table->FreezeRowsCount > 0); // Draw bottom border (always strong) - const bool draw_separating_border = unfreeze_rows || (table->RowFlags & ImGuiTableRowFlags_Headers); + const bool draw_separating_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); if (draw_separating_border) if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderOuterColor); + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong); // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and get the new cursor position. @@ -1801,7 +1824,7 @@ void ImGui::TableAutoHeaders() const char* name = TableGetColumnName(column_n); - // FIXME-TABLE: Test custom user elements + // [DEBUG] Test custom user elements #if 0 if (column_n < 2) { From 325b4c69ba97aea5c7919fec13ff917dd658d03f Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 30 Dec 2019 16:31:18 +0100 Subject: [PATCH 015/144] Tables: Moved border colors to the Style (maybe temporarily?) instead of hardcoding them. --- imgui.cpp | 2 ++ imgui.h | 2 ++ imgui_demo.cpp | 10 ++++++---- imgui_draw.cpp | 6 ++++++ imgui_tables.cpp | 10 +++------- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 660e2b55..90ff9eff 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2550,6 +2550,8 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_PlotHistogram: return "PlotHistogram"; case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; case ImGuiCol_TableHeaderBg: return "TableHeaderBg"; + case ImGuiCol_TableBorderStrong: return "TableBorderStrong"; + case ImGuiCol_TableBorderLight: return "TableBorderLight"; case ImGuiCol_TableRowBg: return "TableRowBg"; case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; diff --git a/imgui.h b/imgui.h index c9920866..006f4ee9 100644 --- a/imgui.h +++ b/imgui.h @@ -1324,6 +1324,8 @@ enum ImGuiCol_ ImGuiCol_PlotHistogram, ImGuiCol_PlotHistogramHovered, ImGuiCol_TableHeaderBg, // Table header background + ImGuiCol_TableBorderStrong, // Table outer and header borders (prefer using Alpha=1.0 here) + ImGuiCol_TableBorderLight, // Table inner borders (prefer using Alpha=1.0 here) ImGuiCol_TableRowBg, // Table row background (even rows) ImGuiCol_TableRowBgAlt, // Table row background (odd rows) ImGuiCol_TextSelectedBg, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d4c8f643..10c67cbf 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3313,10 +3313,12 @@ static void ShowDemoWindowTables() // About Styling of tables // Most settings are configured on a per-table basis via the flags passed to BeginTable() and TableSetupColumns APIs. // There are however a few settings that a shared and part of the ImGuiStyle structure: - // style.CellPadding // Padding within each cell - // style.Colors[ImGuiCol_TableHeaderBg] // Table header background - // style.Colors[ImGuiCol_TableRowBg] // Table row background when ImGuiTableFlags_RowBg is enabled (even rows) - // style.Colors[ImGuiCol_TableRowBgAlt] // Table row background when ImGuiTableFlags_RowBg is enabled (odds rows) + // style.CellPadding // Padding within each cell + // style.Colors[ImGuiCol_TableHeaderBg] // Table header background + // style.Colors[ImGuiCol_TableBorderStrong] // Table outer and header borders + // style.Colors[ImGuiCol_TableBorderLight] // Table inner borders + // style.Colors[ImGuiCol_TableRowBg] // Table row background when ImGuiTableFlags_RowBg is enabled (even rows) + // style.Colors[ImGuiCol_TableRowBgAlt] // Table row background when ImGuiTableFlags_RowBg is enabled (odds rows) // Demos if (open_action != -1) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e915dd76..dfe11f43 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -223,6 +223,8 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); @@ -281,6 +283,8 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); colors[ImGuiCol_TableHeaderBg] = ImVec4(0.27f, 0.27f, 0.38f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.45f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); @@ -340,6 +344,8 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.57f, 0.57f, 0.64f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); // Prefer using Alpha=1.0 here colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.07f); colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f36eccd5..2bac1155 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -267,13 +267,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->HoveredColumnBorder = -1; table->RightMostActiveColumn = -1; - // FIXME-TABLE FIXME-STYLE: Using opaque colors facilitate overlapping elements of the grid - //table->BorderOuterColor = GetColorU32(ImGuiCol_Separator, 1.00f); - //table->BorderInnerColor = GetColorU32(ImGuiCol_Separator, 0.60f); - table->BorderColorStrong = GetColorU32(ImVec4(0.31f, 0.31f, 0.35f, 1.00f)); - table->BorderColorLight = GetColorU32(ImVec4(0.23f, 0.23f, 0.25f, 1.00f)); - //table->BorderOuterColor = IM_COL32(255, 0, 0, 255); - //table->BorderInnerColor = IM_COL32(255, 255, 0, 255); + // Using opaque colors facilitate overlapping elements of the grid + table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); + table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); From 416e9bb38d14b52f624ee6f878da670a612bbaa8 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 3 Jan 2020 00:31:06 +0100 Subject: [PATCH 016/144] Tables: Clarify internal calculations of row height so that TableGetCellRect() include expected paddings. Add demo code. Comments. Remove misleading commented-out flags for now. --- imgui.h | 4 ---- imgui_demo.cpp | 21 +++++++++++++++++++++ imgui_tables.cpp | 20 ++++++++++++-------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/imgui.h b/imgui.h index 006f4ee9..3577fe28 100644 --- a/imgui.h +++ b/imgui.h @@ -1081,10 +1081,6 @@ enum ImGuiTableColumnFlags_ // [Internal] Combinations and masks ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, ImGuiTableColumnFlags_NoDirectResize_ = 1 << 20 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) - //ImGuiTableColumnFlags_AlignLeft = 1 << 14, - //ImGuiTableColumnFlags_AlignCenter = 1 << 15, - //ImGuiTableColumnFlags_AlignRight = 1 << 16, - //ImGuiTableColumnFlags_AlignMask_ = ImGuiTableColumnFlags_AlignLeft | ImGuiTableColumnFlags_AlignCenter | ImGuiTableColumnFlags_AlignRight }; // Flags for ImGui::TableNextRow() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 10c67cbf..25d035b5 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3842,6 +3842,27 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Row height")) + { + HelpMarker("You can pass a 'min_row_height' to TableNextRow().\n\nRows are padded with 'style.CellPadding.y' on top and bottom, so effectively the minimum row height will always be >= 'style.CellPadding.y * 2.0f'.\n\nWe cannot honor a _maximum_ row height as that would requires a unique clipping rectangle per row."); + if (ImGui::BeginTable("##2ways", 2, ImGuiTableFlags_Borders)) + { + float min_row_height = ImGui::GetFontSize() + ImGui::GetStyle().CellPadding.y * 2.0f; + ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); + ImGui::Text("min_row_height = %.2f", min_row_height); + for (int row = 0; row < 10; row++) + { + min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); + ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); + ImGui::Text("min_row_height = %.2f", min_row_height); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + static const char* template_items_names[] = { "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2bac1155..d33654c9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -724,9 +724,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) //if (column->Flags & ImGuiTableColumnFlags_AlignRight) - // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]); + // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen); //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) - // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->WidthContent[0]), 0.5f); + // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); // Reset content width variables const float initial_max_pos_x = column->MinX + table->CellPaddingX1; @@ -1401,8 +1401,10 @@ void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float min_row_height) table->RowFlags = row_flags; TableBeginRow(table); - // We honor min_height requested by user, but cannot guarantee per-row maximum height as that would essentially require a unique clipping rectangle per-cell. - table->RowPosY2 += min_row_height; + // We honor min_row_height requested by user, but cannot guarantee per-row maximum height, + // because that would essentially require a unique clipping rectangle per-cell. + table->RowPosY2 += table->CellPaddingY * 2.0f; + table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + min_row_height); TableBeginCell(table, 0); } @@ -1447,8 +1449,6 @@ void ImGui::TableEndRow(ImGuiTable* table) TableEndCell(table); - table->RowPosY2 += table->CellPaddingY; - // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. // However it is likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. window->DC.CursorPos.y = table->RowPosY2; @@ -1605,7 +1605,7 @@ void ImGui::TableEndCell(ImGuiTable* table) else p_max_pos_x = table->IsFreezeRowsPassed ? &column->ContentMaxPosRowsUnfrozen : &column->ContentMaxPosRowsFrozen; *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x); - table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y); + table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY); // Propagate text baseline for the entire row // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. @@ -1682,6 +1682,9 @@ bool ImGui::TableSetColumnIndex(int column_idx) return (table->VisibleMaskByIndex & ((ImU64)1 << column_idx)) != 0; } +// Return the cell rectangle based on currently known height. +// Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations. +// The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it. ImRect ImGui::TableGetCellRect() { ImGuiContext& g = *GImGui; @@ -1805,7 +1808,7 @@ void ImGui::TableAutoHeaders() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); - TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight()); + TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight() + g.Style.CellPadding.y * 2.0f); if (window->SkipItems) return; @@ -1909,6 +1912,7 @@ void ImGui::TableHeader(const char* label) float row_height = GetTextLineHeight(); ImRect cell_r = TableGetCellRect(); + //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] ImRect work_r = cell_r; work_r.Min.x = window->DC.CursorPos.x; work_r.Max.y = work_r.Min.y + row_height; From e85c226da40f9bd30f5dc562fe32d590b913ca3c Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 3 Jan 2020 18:27:11 +0100 Subject: [PATCH 017/144] Tables: Fix reordering across hidden columns. Fix for frozen columns to never be larger than scrolling visible rect width. --- imgui_tables.cpp | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d33654c9..b17c1369 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -346,12 +346,26 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->HeldHeaderColumn = -1; if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) { - IM_ASSERT(table->ReorderColumnDir == -1 || table->ReorderColumnDir == +1); + // We need to handle reordering across hidden columns. + // In the configuration below, moving C to the right of E will lead to: + // ... C [D] E ---> ... [D] E C (Column name/index) + // ... 2 3 4 ... 2 3 4 (Display order) + const int reorder_dir = table->ReorderColumnDir; + IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); - ImGuiTableColumn* dragged_column = &table->Columns[table->ReorderColumn]; - ImGuiTableColumn* target_column = &table->Columns[(table->ReorderColumnDir == -1) ? dragged_column->PrevActiveColumn : dragged_column->NextActiveColumn]; - ImSwap(table->DisplayOrder[dragged_column->IndexDisplayOrder], table->DisplayOrder[target_column->IndexDisplayOrder]); - ImSwap(dragged_column->IndexDisplayOrder, target_column->IndexDisplayOrder); + ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; + ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevActiveColumn : src_column->NextActiveColumn]; + IM_UNUSED(dst_column); + const int src_order = src_column->IndexDisplayOrder; + const int dst_order = dst_column->IndexDisplayOrder; + src_column->IndexDisplayOrder = (ImS8)dst_order; + for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) + table->Columns[table->DisplayOrder[order_n]].IndexDisplayOrder -= (ImS8)reorder_dir; + IM_ASSERT(dst_column->IndexDisplayOrder == dst_order - reorder_dir); + + // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], rebuild the later from the former. + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->DisplayOrder[table->Columns[column_n].IndexDisplayOrder] = (ImS8)column_n; table->ReorderColumnDir = 0; table->IsSettingsDirty = true; } @@ -690,14 +704,21 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) continue; } - // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. - // Because of this we also know that all of the columns will always fit in table->WorkRect and therefore in table->InnerRect (because ScrollX is off) - if (!(table->Flags & ImGuiTableFlags_ScrollX)) + float max_x = FLT_MAX; + if (table->Flags & ImGuiTableFlags_ScrollX) { - float max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; - if (offset_x + column->WidthGiven > max_x) - column->WidthGiven = ImMax(max_x - offset_x, min_column_width); + // Frozen columns can't reach beyond visible width else scrolling will naturally break. + if (order_n < table->FreezeColumnsRequest) + max_x = table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - order_n) * min_column_width; } + else + { + // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. + // Because of this we also know that all of the columns will always fit in table->WorkRect and therefore in table->InnerRect (because ScrollX is off) + max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; + } + if (offset_x + column->WidthGiven > max_x) + column->WidthGiven = ImMax(max_x - offset_x, min_column_width); column->MinX = offset_x; column->MaxX = column->MinX + column->WidthGiven; From 164caa2db7c64ab5c4c8dd059d5e548e985e64b2 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 6 Jan 2020 11:53:04 +0100 Subject: [PATCH 018/144] Tables: Support for multi-line columns name. Renaming of some fields from BackupXXX to HostXXX. Comments. --- imgui.cpp | 2 +- imgui.h | 5 ++-- imgui_demo.cpp | 2 ++ imgui_internal.h | 8 +++--- imgui_tables.cpp | 63 ++++++++++++++++++++++++++++-------------------- 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 90ff9eff..c8023fdb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2142,7 +2142,7 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) static bool GetSkipItemForListClipping() { ImGuiContext& g = *GImGui; - return (g.CurrentTable ? g.CurrentTable->BackupSkipItems : g.CurrentWindow->SkipItems); + return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); } // Helper to calculate coarse clipping of large list of evenly sized items. diff --git a/imgui.h b/imgui.h index 3577fe28..e0f51274 100644 --- a/imgui.h +++ b/imgui.h @@ -693,7 +693,8 @@ namespace ImGui IMGUI_API void TableHeader(const char* label); // submit one header cell manually. // Tables: Sorting // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. - // - Read ->SpecsChanged to tell if the specs have changed since last call. + // - You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. + // - Don't hold on this structure over multiple frames. IMGUI_API const ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). // Tab Bars, Tabs @@ -1856,7 +1857,7 @@ struct ImGuiTableSortSpecs { const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. - bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. + bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. Use this to sort again! ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsChanged = false; ColumnsMask = 0x00; } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 25d035b5..f1c40e0b 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3916,6 +3916,7 @@ static void ShowDemoWindowTables() { MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + MyItem::s_current_sort_specs = NULL; } // Display data @@ -4104,6 +4105,7 @@ static void ShowDemoWindowTables() { MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + MyItem::s_current_sort_specs = NULL; } items_need_sort = false; diff --git a/imgui_internal.h b/imgui_internal.h index f897815c..9b980e0d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1918,9 +1918,11 @@ struct ImGuiTable float ResizedColumnNextWidth; ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect WorkRect; - ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. ImRect InnerClipRect; ImRect BackgroundClipRect; // We use this to cpu-clip cell background color fill + ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. + ImRect HostWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImVec2 HostCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() ImGuiWindow* OuterWindow; // Parent window for the table ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names @@ -1956,9 +1958,7 @@ struct ImGuiTable bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) bool IsResetDisplayOrderRequest; bool IsFreezeRowsPassed; // Set when we got past the frozen row (the first one). - bool BackupSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis - ImRect BackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() - ImVec2 BackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis ImGuiTable() { diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b17c1369..24519c97 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -232,6 +232,13 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG PushID(instance_id); } + // Backup a copy of host window members we will modify + ImGuiWindow* inner_window = table->InnerWindow; + table->HostClipRect = inner_window->ClipRect; + table->HostSkipItems = inner_window->SkipItems; + table->HostWorkRect = inner_window->WorkRect; + table->HostCursorMaxPos = inner_window->DC.CursorMaxPos; + // Borders // - None ........Content..... Pad .....Content........ // - VOuter | Pad ..Content..... Pad .....Content.. Pad | // FIXME-TABLE: Not handled properly @@ -239,7 +246,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // - VOuter+VInner | Pad ..Content.. Pad | Pad ..Content.. Pad | const bool has_cell_padding_x = (flags & ImGuiTableFlags_BordersVOuter) != 0; - ImGuiWindow* inner_window = table->InnerWindow; table->CellPaddingX1 = has_cell_padding_x ? g.Style.CellPadding.x + 1.0f : 0.0f; table->CellPaddingX2 = has_cell_padding_x ? g.Style.CellPadding.x : 0.0f; table->CellPaddingY = g.Style.CellPadding.y; @@ -249,7 +255,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->CurrentRow = -1; table->RowBgColorCounter = 0; table->LastRowFlags = ImGuiTableRowFlags_None; - table->HostClipRect = inner_window->ClipRect; table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWith(table->HostClipRect); @@ -306,11 +311,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsSettingsRequestLoad) TableLoadSettings(table); - // Grab a copy of window fields we will modify - table->BackupSkipItems = inner_window->SkipItems; - table->BackupWorkRect = inner_window->WorkRect; - table->BackupCursorMaxPos = inner_window->DC.CursorMaxPos; - // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were misleading "out of table" output will be confusing to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. @@ -756,7 +756,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Don't decrement auto-fit counters until container window got a chance to submit its items - if (table->BackupSkipItems == false) + if (table->HostSkipItems == false) { column->AutoFitQueue >>= 1; column->CannotSkipItemsQueue >>= 1; @@ -896,8 +896,8 @@ void ImGui::EndTable() TableEndRow(table); // Finalize table height - inner_window->SkipItems = table->BackupSkipItems; - inner_window->DC.CursorMaxPos = table->BackupCursorMaxPos; + inner_window->SkipItems = table->HostSkipItems; + inner_window->DC.CursorMaxPos = table->HostCursorMaxPos; if (inner_window != outer_window) { table->OuterRect.Max.y = ImMax(table->OuterRect.Max.y, inner_window->Pos.y + inner_window->Size.y); @@ -970,8 +970,8 @@ void ImGui::EndTable() } // Layout in outer window - inner_window->WorkRect = table->BackupWorkRect; - inner_window->SkipItems = table->BackupSkipItems; + inner_window->WorkRect = table->HostWorkRect; + inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; outer_window->DC.ColumnsOffset.x = 0.0f; if (inner_window != outer_window) @@ -1594,7 +1594,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) // FIXME-COLUMNS: Setup baseline, preserve across columns (how can we obtain first line baseline tho..) // window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); - window->SkipItems = column->IsClipped ? true : table->BackupSkipItems; + window->SkipItems = column->IsClipped ? true : table->HostSkipItems; if (table->Flags & ImGuiTableFlags_NoClipX) { table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); @@ -1828,15 +1828,23 @@ void ImGui::TableAutoHeaders() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); + const int columns_count = table->ColumnsCount; - TableNextRow(ImGuiTableRowFlags_Headers, GetTextLineHeight() + g.Style.CellPadding.y * 2.0f); + // Calculate row height (for the unlikely case that labels may be are multi-line) + float row_height = GetTextLineHeight(); + for (int column_n = 0; column_n < columns_count; column_n++) + if (TableGetColumnIsVisible(column_n)) + row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); + row_height += g.Style.CellPadding.y * 2.0f; + + // Open row + TableNextRow(ImGuiTableRowFlags_Headers, row_height); if (window->SkipItems) return; // This for loop is constructed to not make use of internal functions, // as this is intended to be a base template to copy and build from. int open_context_popup = INT_MAX; - const int columns_count = table->ColumnsCount; for (int column_n = 0; column_n < columns_count; column_n++) { if (!TableSetColumnIndex(column_n)) @@ -1873,7 +1881,7 @@ void ImGui::TableAutoHeaders() // FIXME-TABLE: This is not user-land code any more... // FIXME-TABLE: Need to explain why this is here! - window->SkipItems = table->BackupSkipItems; + window->SkipItems = table->HostSkipItems; // Allow opening popup from the right-most section after the last column // FIXME-TABLE: This is not user-land code any more... perhaps instead we should expose hovered column. @@ -1918,6 +1926,7 @@ void ImGui::TableAutoHeaders() // Emit a column header (text + optional sort order) // We cpu-clip text here so that all columns headers can be merged into a same draw call. // FIXME-TABLE: Should hold a selection state. +// FIXME-TABLE: Style confusion between CellPadding.y and FramePadding.y void ImGui::TableHeader(const char* label) { ImGuiContext& g = *GImGui; @@ -1931,19 +1940,21 @@ void ImGui::TableHeader(const char* label) const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; - float row_height = GetTextLineHeight(); - ImRect cell_r = TableGetCellRect(); - //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] - ImRect work_r = cell_r; - work_r.Min.x = window->DC.CursorPos.x; - work_r.Max.y = work_r.Min.y + row_height; - // Label if (label == NULL) label = ""; const char* label_end = FindRenderedTextEnd(label); ImVec2 label_size = CalcTextSize(label, label_end, true); ImVec2 label_pos = window->DC.CursorPos; + + // If we already got a row height, there's use that. + ImRect cell_r = TableGetCellRect(); + float label_height = ImMax(label_size.y, cell_r.GetHeight() - g.Style.CellPadding.y * 2.0f); + + //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + ImRect work_r = cell_r; + work_r.Min.x = window->DC.CursorPos.x; + work_r.Max.y = work_r.Min.y + label_height; float ellipsis_max = work_r.Max.x; // Selectable @@ -1954,7 +1965,7 @@ void ImGui::TableHeader(const char* label) // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceNo); - const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, row_height)); + const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, label_height)); const bool held = IsItemActive(); if (held) table->HeldHeaderColumn = (ImS8)column_n; @@ -2016,7 +2027,7 @@ void ImGui::TableHeader(const char* label) // Render clipped label // Clipping here ensure that in the majority of situations, all our header cells will be merged into a single draw call. //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); - RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + row_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. // FIXME-TABLE: Clarify policies of how label width and potential decorations (arrows) fit into auto-resize of the column @@ -2066,7 +2077,7 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click } // Return NULL if no sort specs. -// Return ->WantSort == true when the specs have changed since the last query. +// You can sort your data again when 'SpecsChanged == true'.It will be true with sorting specs have changed since last call, or the first time. const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui; From 31de16106632bdf93763653fa9f3e71621aff883 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 6 Jan 2020 12:21:15 +0100 Subject: [PATCH 019/144] Tables: Fix for hiding first column (fix fcceff5c + reading PrevLineTextBaseOffset in EndCell of inactive column). --- imgui_tables.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 24519c97..b3958d23 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1449,6 +1449,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) table->RowPosY1 = table->RowPosY2 = next_y1; table->RowTextBaseline = 0.0f; + window->DC.PrevLineTextBaseOffset = 0.0f; window->DC.CursorMaxPos.y = next_y1; // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. @@ -1569,6 +1570,7 @@ void ImGui::TableEndRow(ImGuiTable* table) // [Internal] Called by TableNextRow()TableNextCell()! // This is called a lot, so we need to be mindful of unnecessary overhead. +// FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. void ImGui::TableBeginCell(ImGuiTable* table, int column_no) { table->CurrentColumn = column_no; @@ -1839,7 +1841,7 @@ void ImGui::TableAutoHeaders() // Open row TableNextRow(ImGuiTableRowFlags_Headers, row_height); - if (window->SkipItems) + if (table->HostSkipItems) // Merely an optimization return; // This for loop is constructed to not make use of internal functions, @@ -1879,8 +1881,7 @@ void ImGui::TableAutoHeaders() open_context_popup = column_n; } - // FIXME-TABLE: This is not user-land code any more... - // FIXME-TABLE: Need to explain why this is here! + // FIXME-TABLE: This is not user-land code any more + need to explain WHY this is here! window->SkipItems = table->HostSkipItems; // Allow opening popup from the right-most section after the last column From 2958e3731009bb9e8e16c633fed87e003cb934fd Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 9 Jan 2020 17:31:26 +0100 Subject: [PATCH 020/144] Tables: Storing per-column SkipItems as a shortcut. Comments, Spacings. # Conflicts: # imgui_internal.h --- imgui.h | 2 +- imgui_demo.cpp | 2 +- imgui_internal.h | 21 +++++++++------------ imgui_tables.cpp | 34 ++++++++++++++++++---------------- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/imgui.h b/imgui.h index e0f51274..e03b8985 100644 --- a/imgui.h +++ b/imgui.h @@ -694,7 +694,7 @@ namespace ImGui // Tables: Sorting // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. // - You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. - // - Don't hold on this structure over multiple frames. + // - Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! IMGUI_API const ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). // Tab Bars, Tabs diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f1c40e0b..5c300e54 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3618,7 +3618,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); - + if (ImGui::BeginTable("##table1", 7, flags, size)) { ImGui::TableSetupColumn("Line #", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of ImGuiTableFlags_ScrollFreezeLeftColumn diff --git a/imgui_internal.h b/imgui_internal.h index 9b980e0d..1b3eb592 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1823,11 +1823,12 @@ struct ImGuiTabBar #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code #define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. -// [Internal] sizeof() ~ 96 +// [Internal] sizeof() ~ 100 struct ImGuiTableColumn { + ImRect ClipRect; // Clipping rectangle for the column ImGuiID UserID; // Optional, value passed to TableSetupColumn() - ImGuiTableColumnFlags FlagsIn; // Flags as input by user. See ImGuiTableColumnFlags_ + ImGuiTableColumnFlags FlagsIn; // Flags as they were provided by user. See ImGuiTableColumnFlags_ ImGuiTableColumnFlags Flags; // Effective flags. See ImGuiTableColumnFlags_ float ResizeWeight; // ~1.0f. Master width data when (Flags & _WidthStretch) float MinX; // Absolute positions @@ -1836,19 +1837,19 @@ struct ImGuiTableColumn float WidthGiven; // == (MaxX - MinX). FIXME-TABLE: Store all persistent width in multiple of FontSize? float StartXRows; // Start position for the frame, currently ~(MinX + CellPaddingX) float StartXHeaders; - ImS16 ContentWidthRowsFrozen; // Contents width. Because freezing is non correlated from headers we need all 4 variants (ImDrawCmd merging uses different data than alignment code). - ImS16 ContentWidthRowsUnfrozen; // (encoded as ImS16 because we actually rarely use those width) - ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls - ImS16 ContentWidthHeadersDesired; float ContentMaxPosRowsFrozen; // Submitted contents absolute maximum position, from which we can infer width. float ContentMaxPosRowsUnfrozen; // (kept as float because we need to manipulate those between each cell change) float ContentMaxPosHeadersUsed; float ContentMaxPosHeadersDesired; - ImRect ClipRect; - ImS16 NameOffset; // Offset into parent ColumnsName[] + ImS16 ContentWidthRowsFrozen; // Contents width. Because row freezing is not correlated with headers/not-headers we need all 4 variants (ImDrawCmd merging uses different data than alignment code). + ImS16 ContentWidthRowsUnfrozen; // (encoded as ImS16 because we actually rarely use those width) + ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls + ImS16 ContentWidthHeadersDesired; + ImS16 NameOffset; // Offset into parent ColumnsNames[] bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. bool NextIsActive; bool IsClipped; // Set when not overlapping the host window clipping rectangle. We don't use the opposite "!Visible" name because Clipped can be altered by events. + bool SkipItems; ImS8 IndexDisplayOrder; // Index within DisplayOrder[] (column may be reordered by users) ImS8 IndexWithinActiveSet; // Index within active set (<= IndexOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] @@ -2178,10 +2179,6 @@ namespace ImGui IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); // Tables - //IMGUI_API int GetTableColumnNo(); - //IMGUI_API bool SetTableColumnNo(int column_n); - //IMGUI_API int GetTableLineNo(); - IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void TableBeginUpdateColumns(ImGuiTable* table); IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b3958d23..781392b8 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -68,20 +68,22 @@ // - TableBeginUpdateColumns() - apply resize/order requests, lock columns active state, order // - TableSetupColumn() user submit columns details (optional) // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) -// - TableSortSpecsClickColumn() +// - TableSortSpecsClickColumn() - when clicked: alter sort order and sort direction // - TableGetSortSpecs() user queries updated sort specs (optional) // - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() -// - TableUpdateLayout() - called by the FIRST call to TableNextRow() -// - TableUpdateDrawChannels() - setup ImDrawList channels -// - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission -// - TableDrawContextMenu() - draw right-click context menu +// - TableUpdateLayout() - called by the FIRST call to TableNextRow()! +// - TableUpdateDrawChannels() - setup ImDrawList channels +// - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission +// - TableDrawContextMenu() - draw right-click context menu +// - TableEndCell() - close existing cell if not the first time +// - TableBeginCell() - enter into current cell // - [...] user emit contents // - EndTable() user ends the table // - TableDrawBorders() - draw outer borders, inner vertical borders // - TableDrawMergeChannels() - merge draw channels if clipping isn't required // - TableSetColumnWidth() - apply resizing width -// - TableUpdateColumnsWeightFromWidth() -// - EndChild() - (if ScrollX/ScrollY is set) +// - TableUpdateColumnsWeightFromWidth() - recompute columns weights (of weighted columns) from their respective width +// - EndChild() - (if ScrollX/ScrollY is set) //----------------------------------------------------------------------------- // Configuration @@ -700,7 +702,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Max.x = offset_x; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); - column->IsClipped = true; + column->IsClipped = column->SkipItems = true; continue; } @@ -731,6 +733,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.ClipWithFull(host_clip_rect); column->IsClipped = (column->ClipRect.Max.x <= column->ClipRect.Min.x) && (column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0; + column->SkipItems = column->IsClipped || table->HostSkipItems; if (column->IsClipped) { // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. @@ -1580,9 +1583,10 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) const float start_x = (table->RowFlags & ImGuiTableRowFlags_Headers) ? column->StartXHeaders : column->StartXRows; window->DC.LastItemId = 0; - window->DC.CursorPos = ImVec2(start_x, table->RowPosY1 + table->CellPaddingY); + window->DC.CursorPos.x = start_x; + window->DC.CursorPos.y = table->RowPosY1 + table->CellPaddingY; window->DC.CursorMaxPos.x = window->DC.CursorPos.x; - window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT // FIXME-TABLE: Recurse + window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; window->WorkRect.Min.y = window->DC.CursorPos.y; @@ -1593,10 +1597,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) if (!column->IsActive) window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); - // FIXME-COLUMNS: Setup baseline, preserve across columns (how can we obtain first line baseline tho..) - // window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); - - window->SkipItems = column->IsClipped ? true : table->HostSkipItems; + window->SkipItems = column->SkipItems; if (table->Flags & ImGuiTableFlags_NoClipX) { table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); @@ -1615,7 +1616,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) } } -// [Internal] Called by TableNextRow()TableNextCell()! +// [Internal] Called by TableNextRow()/TableNextCell()! void ImGui::TableEndCell(ImGuiTable* table) { ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; @@ -2078,7 +2079,8 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click } // Return NULL if no sort specs. -// You can sort your data again when 'SpecsChanged == true'.It will be true with sorting specs have changed since last call, or the first time. +// You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. +// Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui; From 0e7b3f2f2f9e0008462ce6acfd64c4b520390e00 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 9 Jan 2020 21:10:45 +0100 Subject: [PATCH 021/144] Tables: Made only first column honor Indent by default (like Columns api) and exposed flags. Added simple Tree demo. --- imgui.h | 3 ++ imgui_demo.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++++-- imgui_internal.h | 1 + imgui_tables.cpp | 7 ++++- 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index e03b8985..47dfe1b9 100644 --- a/imgui.h +++ b/imgui.h @@ -1078,9 +1078,12 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_NoHeaderWidth = 1 << 11, // Header width don't contribute to automatic column width. ImGuiTableColumnFlags_PreferSortAscending = 1 << 12, // Make the initial sort direction Ascending when first sorting on this column (default). ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. + ImGuiTableColumnFlags_IndentEnable = 1 << 14, // Use current Indent value when entering cell (default for 1st column). + ImGuiTableColumnFlags_IndentDisable = 1 << 15, // Ignore current Indent value when entering cell (default for columns after the 1st one). Indentation changes _within_ the cell will still be honored. // [Internal] Combinations and masks ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, + ImGuiTableColumnFlags_IndentMask_ = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable, ImGuiTableColumnFlags_NoDirectResize_ = 1 << 20 // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5c300e54..570fa75c 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3679,6 +3679,8 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("_NoSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSortDescending); ImGui::CheckboxFlags("_PreferSortAscending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortAscending); ImGui::CheckboxFlags("_PreferSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortDescending); + ImGui::CheckboxFlags("_IndentEnable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentEnable); + ImGui::CheckboxFlags("_IndentDisable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentDisable); ImGui::PopID(); ImGui::PopStyleVar(2); } @@ -3687,20 +3689,23 @@ static void ShowDemoWindowTables() // Create the real table we care about for the example! const ImGuiTableFlags flags = ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable; - if (ImGui::BeginTable("##table1", column_count, flags)) + if (ImGui::BeginTable("##table", column_count, flags)) { for (int column = 0; column < column_count; column++) ImGui::TableSetupColumn(column_names[column], column_flags[column]); ImGui::TableAutoHeaders(); for (int row = 0; row < 8; row++) { + ImGui::Indent(2.0f); // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags. ImGui::TableNextRow(); for (int column = 0; column < column_count; column++) { ImGui::TableSetColumnIndex(column); - ImGui::Text("Hello %s", ImGui::TableGetColumnName(column)); + ImGui::Text("%s %s", (column == 0) ? "Indented" : "Hello", ImGui::TableGetColumnName(column)); } } + ImGui::Unindent(2.0f * 8.0f); + ImGui::EndTable(); } ImGui::TreePop(); @@ -3863,6 +3868,78 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Tree view")) + { + static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersHOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg; + //ImGui::CheckboxFlags("ImGuiTableFlags_Scroll", (unsigned int*)&flags, ImGuiTableFlags_Scroll); + //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); + + if (ImGui::BeginTable("##3ways", 3, flags)) + { + // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 10); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 20); + ImGui::TableAutoHeaders(); + + // Simple storage to output a dummy file-system. + struct MyTreeNode + { + const char* Name; + const char* Type; + int Size; + int ChildIdx; + int ChildCount; + static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes) + { + ImGui::TableNextRow(); + const bool is_folder = (node->ChildCount > 0); + if (is_folder) + { + bool open = ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextCell(); + ImGui::TextDisabled("--"); + ImGui::TableNextCell(); + ImGui::TextUnformatted(node->Type); + if (open) + { + for (int child_n = 0; child_n < node->ChildCount; child_n++) + DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes); + ImGui::TreePop(); + } + } + else + { + ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextCell(); + ImGui::Text("%d", node->Size); + ImGui::TableNextCell(); + ImGui::TextUnformatted(node->Type); + } + } + }; + static const MyTreeNode nodes[] = + { + { "Root", "Folder", -1, 1, 3 }, // 0 + { "Music", "Folder", -1, 4, 2 }, // 1 + { "Textures", "Folder", -1, 6, 3 }, // 2 + { "desktop.ini", "System file", 1024, -1,-1 }, // 3 + { "File1_a.wav", "Audio file", 123000, -1,-1 }, // 4 + { "File1_b.wav", "Audio file", 456000, -1,-1 }, // 5 + { "Image001.png", "Image file", 203128, -1,-1 }, // 6 + { "Copy of Image001.png", "Image file", 203256, -1,-1 }, // 7 + { "Copy of Image001 (Final2).png","Image file", 203512, -1,-1 }, // 8 + }; + + MyTreeNode::DisplayNode(&nodes[0], nodes); + + ImGui::EndTable(); + } + ImGui::TreePop(); + } + static const char* template_items_names[] = { "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", diff --git a/imgui_internal.h b/imgui_internal.h index 1b3eb592..8fdffd6c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1908,6 +1908,7 @@ struct ImGuiTable ImU32 BorderColorLight; float BorderX1; float BorderX2; + float HostIndentX; float CellPaddingX1; // Padding from each borders float CellPaddingX2; float CellPaddingY; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 781392b8..905375fe 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -236,6 +236,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Backup a copy of host window members we will modify ImGuiWindow* inner_window = table->InnerWindow; + table->HostIndentX = inner_window->DC.Indent.x; table->HostClipRect = inner_window->ClipRect; table->HostSkipItems = inner_window->SkipItems; table->HostWorkRect = inner_window->WorkRect; @@ -552,6 +553,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Adjust flags: default width mode + weighted columns are not allowed when auto extending // FIXME-TABLE: Clarify why we need to do this again here and not just in TableSetupColumn() column->Flags = TableFixColumnFlags(table, column->FlagsIn); + if ((column->Flags & ImGuiTableColumnFlags_IndentMask_) == 0) + column->Flags |= (column_n == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable; // We have a unusual edge case where if the user doesn't call TableGetSortSpecs() but has sorting enabled // or varying sorting flags, we still want the sorting arrows to honor those flags. @@ -1580,7 +1583,9 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) ImGuiTableColumn* column = &table->Columns[column_no]; ImGuiWindow* window = table->InnerWindow; - const float start_x = (table->RowFlags & ImGuiTableRowFlags_Headers) ? column->StartXHeaders : column->StartXRows; + float start_x = (table->RowFlags & ImGuiTableRowFlags_Headers) ? column->StartXHeaders : column->StartXRows; + if (column->Flags & ImGuiTableColumnFlags_IndentEnable) + start_x += window->DC.Indent.x - table->HostIndentX; window->DC.LastItemId = 0; window->DC.CursorPos.x = start_x; From 5431cbd3f026f98c3f7cec6d41be0c11a108b178 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 9 Jan 2020 22:02:16 +0100 Subject: [PATCH 022/144] Tables: Honor width/weight passed to TableSetupColumn() after .ini load since we don't actually restore that data currently. Demo: Remove filter from Advanced Table demo since it's breaking with clipping. --- imgui_demo.cpp | 22 +++++++++++----------- imgui_internal.h | 5 ++--- imgui_tables.cpp | 11 ++++++++--- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 570fa75c..02a12522 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3661,14 +3661,14 @@ static void ShowDemoWindowTables() { for (int column = 0; column < column_count; column++) { - ImGui::TableNextCell(); // Make the UI compact because there are so many fields + ImGui::TableNextCell(); ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); ImGui::PushID(column); ImGui::AlignTextToFramePadding(); // FIXME-TABLE: Workaround for wrong text baseline propagation - ImGui::Text("Column '%s'", column_names[column]); + ImGui::Text("Flags for '%s'", column_names[column]); ImGui::CheckboxFlags("_NoResize", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoResize); ImGui::CheckboxFlags("_NoClipX", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoClipX); ImGui::CheckboxFlags("_NoHide", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoHide); @@ -3880,8 +3880,8 @@ static void ShowDemoWindowTables() { // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); - ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 10); - ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 20); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 6); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 10); ImGui::TableAutoHeaders(); // Simple storage to output a dummy file-system. @@ -4043,10 +4043,10 @@ static void ShowDemoWindowTables() static float inner_width_without_scroll = 0.0f; // Fill static float inner_width_with_scroll = 0.0f; // Auto-extend static bool outer_size_enabled = true; - static bool lock_left_column_visibility = false; + static bool lock_first_column_visibility = false; static bool show_headers = true; static bool show_wrapped_text = false; - static ImGuiTextFilter filter; + //static ImGuiTextFilter filter; //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which affects sizing if (ImGui::TreeNodeEx("Options")) { @@ -4130,10 +4130,10 @@ static void ShowDemoWindowTables() ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000); ImGui::Combo("contents_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); - filter.Draw("filter"); + //filter.Draw("filter"); ImGui::Checkbox("show_headers", &show_headers); ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); - ImGui::Checkbox("lock_left_column_visibility", &lock_left_column_visibility); + ImGui::Checkbox("lock_first_column_visibility", &lock_first_column_visibility); ImGui::Unindent(); ImGui::PopItemWidth(); @@ -4168,7 +4168,7 @@ static void ShowDemoWindowTables() // Declare columns // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! - ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | (lock_left_column_visibility ? ImGuiTableColumnFlags_NoHide : 0), -1.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | (lock_first_column_visibility ? ImGuiTableColumnFlags_NoHide : 0), -1.0f, MyItemColumnID_ID); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Name); ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); ImGui::TableSetupColumn("Quantity Long Label", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 1.0f, MyItemColumnID_Quantity);// , ImGuiTableColumnFlags_None | ImGuiTableColumnFlags_WidthAlwaysAutoResize); @@ -4209,8 +4209,8 @@ static void ShowDemoWindowTables() #endif { MyItem* item = &items[row_n]; - if (!filter.PassFilter(item->Name)) - continue; + //if (!filter.PassFilter(item->Name)) + // continue; const bool item_is_selected = selection.contains(item->ID); ImGui::PushID(item->ID); diff --git a/imgui_internal.h b/imgui_internal.h index 8fdffd6c..af10d17e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1830,9 +1830,9 @@ struct ImGuiTableColumn ImGuiID UserID; // Optional, value passed to TableSetupColumn() ImGuiTableColumnFlags FlagsIn; // Flags as they were provided by user. See ImGuiTableColumnFlags_ ImGuiTableColumnFlags Flags; // Effective flags. See ImGuiTableColumnFlags_ - float ResizeWeight; // ~1.0f. Master width data when (Flags & _WidthStretch) float MinX; // Absolute positions float MaxX; + float ResizeWeight; // ~1.0f. Master width data when (Flags & _WidthStretch) float WidthRequested; // Master width data when !(Flags & _WidthStretch) float WidthGiven; // == (MaxX - MinX). FIXME-TABLE: Store all persistent width in multiple of FontSize? float StartXRows; // Start position for the frame, currently ~(MinX + CellPaddingX) @@ -1865,8 +1865,7 @@ struct ImGuiTableColumn ImGuiTableColumn() { memset(this, 0, sizeof(*this)); - ResizeWeight = 1.0f; - WidthRequested = WidthGiven = -1.0f; + ResizeWeight = WidthRequested = WidthGiven = -1.0f; NameOffset = -1; IsActive = NextIsActive = true; IndexDisplayOrder = IndexWithinActiveSet = -1; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 905375fe..1e1f73d7 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -585,7 +585,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else { IM_ASSERT(column->Flags & ImGuiTableColumnFlags_WidthStretch); - IM_ASSERT(column->ResizeWeight > 0.0f); + const int init_size = (column->ResizeWeight < 0.0f); + if (init_size) + column->ResizeWeight = 1.0f; total_weights += column->ResizeWeight; if (table->LeftMostStretchedColumnDisplayOrder == -1) table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->IndexDisplayOrder; @@ -1378,7 +1380,8 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flags = column->Flags; // Initialize defaults - if (table->IsInitializing && !table->IsSettingsLoaded) + // FIXME-TABLE: We don't restore widths/weight so let's avoid using IsSettingsLoaded for now + if (table->IsInitializing && column->WidthRequested < 0.0f && column->ResizeWeight < 0.0f)// && !table->IsSettingsLoaded) { // Init width or weight // Disable auto-fit if a default fixed width has been specified @@ -1396,7 +1399,9 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, { column->ResizeWeight = 1.0f; } - + } + if (table->IsInitializing && !table->IsSettingsLoaded) + { // Init default visibility/sort state if (flags & ImGuiTableColumnFlags_DefaultHide) column->IsActive = column->NextIsActive = false; From f5eee210a0e3d15a6f694cbc69dbf94aa413f421 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 20 Jan 2020 12:41:23 +0100 Subject: [PATCH 023/144] Tables: TableHeader() uses provided row min header rather than incremental one to allow multi-item multi-line in header cells. Demo TableHeader() - will caveat, comments. --- imgui_demo.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ imgui_internal.h | 1 + imgui_tables.cpp | 8 +++++--- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 02a12522..4ee5b0f7 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3940,6 +3940,54 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + // Demonstrate using TableHeader() calls instead of TableAutoHeaders() + // FIXME-TABLE: Currently this doesn't get us feature-parity with TableAutoHeaders(), e.g. missing context menu. Tables API needs some work! + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Custom headers")) + { + const int COLUMNS_COUNT = 3; + if (ImGui::BeginTable("##table1", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable)) + { + ImGui::TableSetupColumn("Apricot"); + ImGui::TableSetupColumn("Banana"); + ImGui::TableSetupColumn("Cherry"); + + // Dummy entire-column selection storage + // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox. + static bool column_selected[3] = {}; + + // Instead of calling TableAutoHeaders() we'll submit custom headers ourselves + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < COLUMNS_COUNT; column++) + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("##checkall", &column_selected[column]); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TableHeader(column_name); + ImGui::PopID(); + } + + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + char buf[32]; + sprintf(buf, "Cell %d,%d", row, column); + ImGui::TableSetColumnIndex(column); + ImGui::Selectable(buf, column_selected[column]); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + static const char* template_items_names[] = { "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", diff --git a/imgui_internal.h b/imgui_internal.h index af10d17e..efb04012 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1898,6 +1898,7 @@ struct ImGuiTable ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with float RowPosY1; float RowPosY2; + float RowMinHeight; // Height submitted to TableNextRow() float RowTextBaseline; ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ ImGuiTableRowFlags LastRowFlags : 16; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 1e1f73d7..87eb64fe 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1419,7 +1419,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, } // Starts into the first cell of a new row -void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float min_row_height) +void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; @@ -1431,12 +1431,13 @@ void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float min_row_height) table->LastRowFlags = table->RowFlags; table->RowFlags = row_flags; + table->RowMinHeight = row_min_height; TableBeginRow(table); // We honor min_row_height requested by user, but cannot guarantee per-row maximum height, // because that would essentially require a unique clipping rectangle per-cell. table->RowPosY2 += table->CellPaddingY * 2.0f; - table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + min_row_height); + table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); TableBeginCell(table, 0); } @@ -1937,6 +1938,7 @@ void ImGui::TableAutoHeaders() // Emit a column header (text + optional sort order) // We cpu-clip text here so that all columns headers can be merged into a same draw call. +// Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader() // FIXME-TABLE: Should hold a selection state. // FIXME-TABLE: Style confusion between CellPadding.y and FramePadding.y void ImGui::TableHeader(const char* label) @@ -1961,7 +1963,7 @@ void ImGui::TableHeader(const char* label) // If we already got a row height, there's use that. ImRect cell_r = TableGetCellRect(); - float label_height = ImMax(label_size.y, cell_r.GetHeight() - g.Style.CellPadding.y * 2.0f); + float label_height = ImMax(label_size.y, table->RowMinHeight - g.Style.CellPadding.y * 2.0f); //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] ImRect work_r = cell_r; From 787a309445d18db5823857c0fb43268f4d1b2efe Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 22 Jan 2020 17:04:54 +0100 Subject: [PATCH 024/144] Tables: Fixed headers closing popups. --- imgui_tables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 87eb64fe..0f3475bb 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1979,7 +1979,7 @@ void ImGui::TableHeader(const char* label) // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceNo); - const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld, ImVec2(0.0f, label_height)); + const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld | ImGuiSelectableFlags_DontClosePopups, ImVec2(0.0f, label_height)); const bool held = IsItemActive(); if (held) table->HeldHeaderColumn = (ImS8)column_n; From 104ec408a890a56778e9980dd5ae3790b71ab4cf Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 6 Feb 2020 18:34:37 +0100 Subject: [PATCH 025/144] Tables: Fixed content size calculation creating feedback loops. Fixed handling of _DefaultSort with _PreferSortXXXflags (@parbo). Comments. --- imgui.h | 4 ++-- imgui_demo.cpp | 2 +- imgui_tables.cpp | 13 ++++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/imgui.h b/imgui.h index 47dfe1b9..3be894f8 100644 --- a/imgui.h +++ b/imgui.h @@ -1066,9 +1066,9 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_None = 0, ImGuiTableColumnFlags_DefaultHide = 1 << 0, // Default as a hidden column. ImGuiTableColumnFlags_DefaultSort = 1 << 1, // Default as a sorting column. - ImGuiTableColumnFlags_WidthFixed = 1 << 2, // Column will keep a fixed size, preferable with horizontal scrolling enabled (default if table sizing policy is SizingPolicyFixedX). + ImGuiTableColumnFlags_WidthFixed = 1 << 2, // Column will keep a fixed size, preferable with horizontal scrolling enabled (default if table sizing policy is SizingPolicyFixedX and table is resizable). ImGuiTableColumnFlags_WidthStretch = 1 << 3, // Column will stretch, preferable with horizontal scrolling disabled (default if table sizing policy is SizingPolicyStretchX). - ImGuiTableColumnFlags_WidthAlwaysAutoResize = 1 << 4, // Column will keep resizing based on submitted contents (with a one frame delay) == Fixed with auto resize + ImGuiTableColumnFlags_WidthAlwaysAutoResize = 1 << 4, // Column will keep resizing based on submitted contents (with a one frame delay) == Fixed with auto resize (default if table sizing policy is SizingPolicyFixedX and table is not resizable). ImGuiTableColumnFlags_NoResize = 1 << 5, // Disable manual resizing. ImGuiTableColumnFlags_NoClipX = 1 << 6, // Disable clipping for this column (all NoClipX columns will render in a same draw command). ImGuiTableColumnFlags_NoSort = 1 << 7, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 4ee5b0f7..22338f79 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3795,7 +3795,7 @@ static void ShowDemoWindowTables() case CT_LongText: ImGui::Text("Some longer text %d,%d\nOver two lines..", row, column); break; case CT_Button: ImGui::Button(label); break; case CT_StretchButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; - case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText(label, text_buf, IM_ARRAYSIZE(text_buf)); break; + case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); break; } } } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 0f3475bb..9798f432 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -487,6 +487,7 @@ static ImGuiTableColumnFlags TableFixColumnFlags(ImGuiTable* table, ImGuiTableCo // Sizing Policy if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) { + // FIXME-TABLE: Inconsistent to promote columns to WidthAlwaysAutoResize if (table->Flags & ImGuiTableFlags_SizingPolicyFixedX) flags |= ((table->Flags & ImGuiTableFlags_Resizable) && !(flags & ImGuiTableColumnFlags_NoResize)) ? ImGuiTableColumnFlags_WidthFixed : ImGuiTableColumnFlags_WidthAlwaysAutoResize; else @@ -536,7 +537,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Compute offset, clip rect for the frame const ImRect work_rect = table->WorkRect; - const float padding_auto_x = table->CellPaddingX1; // Can't make auto padding larger than what WorkRect knows about so right-alignment matches. + const float padding_auto_x = table->CellPaddingX2; // Can't make auto padding larger than what WorkRect knows about so right-alignment matches. const float min_column_width = TableGetMinColumnWidth(); int count_fixed = 0; @@ -595,12 +596,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Layout + // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1); float width_avail; if ((table->Flags & ImGuiTableFlags_ScrollX) && (table->InnerWidth == 0.0f)) width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; else - width_avail = work_rect.GetWidth() - width_spacings - 1.0f; // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible + width_avail = work_rect.GetWidth() - width_spacings - 1.0f; const float width_avail_for_stretched_columns = width_avail - width_fixed; float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; @@ -1406,7 +1408,10 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, if (flags & ImGuiTableColumnFlags_DefaultHide) column->IsActive = column->NextIsActive = false; if (flags & ImGuiTableColumnFlags_DefaultSort) + { column->SortOrder = 0; // Multiple columns using _DefaultSort will be reordered when building the sort specs. + column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); + } } // Store name (append with zero-terminator in contiguous buffer) @@ -2090,7 +2095,7 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click table->IsSortSpecsDirty = true; } -// Return NULL if no sort specs. +// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) // You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() @@ -2439,10 +2444,12 @@ void ImGui::DebugNodeTable(ImGuiTable* table) BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" + "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Desired %d/%d\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", n, column->IndexDisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->WidthGiven, column->WidthRequested, column->ResizeWeight, + column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersDesired, column->UserID, column->Flags, (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", From 643cf6fc8cfe7e489c7f1f6395d237efffbe1756 Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 10 Feb 2020 15:48:08 +0100 Subject: [PATCH 026/144] Tables: Added ImGuiTableFlags_NoKeepColumnsVisible wip flag useful for layout purpose. (WIP) --- imgui.h | 21 +++++++++++---------- imgui_tables.cpp | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/imgui.h b/imgui.h index 3be894f8..067c4f21 100644 --- a/imgui.h +++ b/imgui.h @@ -1040,21 +1040,22 @@ enum ImGuiTableFlags_ ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use WidthStretch policy. Read description above for more details. ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small. // Scrolling - ImGuiTableFlags_ScrollX = 1 << 17, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. - ImGuiTableFlags_ScrollY = 1 << 18, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + ImGuiTableFlags_ScrollX = 1 << 18, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 19, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, - ImGuiTableFlags_ScrollFreezeTopRow = 1 << 19, // We can lock 1 to 3 rows (starting from the top). Use with ScrollY enabled. - ImGuiTableFlags_ScrollFreeze2Rows = 2 << 19, - ImGuiTableFlags_ScrollFreeze3Rows = 3 << 19, - ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << 21, // We can lock 1 to 3 columns (starting from the left). Use with ScrollX enabled. - ImGuiTableFlags_ScrollFreeze2Columns = 2 << 21, - ImGuiTableFlags_ScrollFreeze3Columns = 3 << 21, + ImGuiTableFlags_ScrollFreezeTopRow = 1 << 20, // We can lock 1 to 3 rows (starting from the top). Use with ScrollY enabled. + ImGuiTableFlags_ScrollFreeze2Rows = 2 << 20, + ImGuiTableFlags_ScrollFreeze3Rows = 3 << 20, + ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << 22, // We can lock 1 to 3 columns (starting from the left). Use with ScrollX enabled. + ImGuiTableFlags_ScrollFreeze2Columns = 2 << 22, + ImGuiTableFlags_ScrollFreeze3Columns = 3 << 22, // [Internal] Combinations and masks ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX, - ImGuiTableFlags_ScrollFreezeRowsShift_ = 19, - ImGuiTableFlags_ScrollFreezeColumnsShift_ = 21, + ImGuiTableFlags_ScrollFreezeRowsShift_ = 20, + ImGuiTableFlags_ScrollFreezeColumnsShift_ = 22, ImGuiTableFlags_ScrollFreezeRowsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeRowsShift_, ImGuiTableFlags_ScrollFreezeColumnsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeColumnsShift_ }; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 9798f432..fedf1b35 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -724,7 +724,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. // Because of this we also know that all of the columns will always fit in table->WorkRect and therefore in table->InnerRect (because ScrollX is off) - max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; + if (!(table->Flags & ImGuiTableFlags_NoKeepColumnsVisible)) + max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; } if (offset_x + column->WidthGiven > max_x) column->WidthGiven = ImMax(max_x - offset_x, min_column_width); From ae6fc48f6048b171996ad985bd20c23aef221a40 Mon Sep 17 00:00:00 2001 From: Omar Date: Mon, 17 Feb 2020 15:09:50 +0100 Subject: [PATCH 027/144] Tables: Fix sort direction (issue 3023). Remove SortOrder from ImGuiTableSortSpecsColumn. Made sort arrow smaller. Added debug stuff in metrics. --- imgui.h | 19 +++++++++---------- imgui_demo.cpp | 6 +++--- imgui_tables.cpp | 16 ++++++++++------ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/imgui.h b/imgui.h index 067c4f21..cc380ea0 100644 --- a/imgui.h +++ b/imgui.h @@ -1846,23 +1846,22 @@ struct ImGuiPayload // Sorting specification for one column of a table (sizeof == 8 bytes) struct ImGuiTableSortSpecsColumn { - ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) - ImU8 ColumnIndex; // Index of the column - ImU8 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) - ImS8 SortSign; // +1 or -1 (you can use this or SortDirection, whichever is more convenient for your sort function) - ImS8 SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more convenient for your sort function) + ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) + ImU8 ColumnIndex; // Index of the column + ImU8 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) + ImGuiSortDirection SortDirection : 8; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more convenient for your sort function) - ImGuiTableSortSpecsColumn() { ColumnUserID = 0; ColumnIndex = 0; SortOrder = 0; SortSign = +1; SortDirection = ImGuiSortDirection_Ascending; } + ImGuiTableSortSpecsColumn() { ColumnUserID = 0; ColumnIndex = 0; SortOrder = 0; SortDirection = ImGuiSortDirection_Ascending; } }; // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) // Obtained by calling TableGetSortSpecs() struct ImGuiTableSortSpecs { - const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. - int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. - bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. Use this to sort again! - ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. + const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. + int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. + bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. Use this to sort again! + ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsChanged = false; ColumnsMask = 0x00; } }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 22338f79..b91392ae 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3271,10 +3271,10 @@ struct MyItem case MyItemColumnID_Description: delta = (strcmp(a->Name, b->Name)); break; default: IM_ASSERT(0); break; } - if (delta < 0) - return -1 * sort_spec->SortSign; if (delta > 0) - return +1 * sort_spec->SortSign; + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + if (delta < 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; } // qsort() is instable so always return a way to differenciate items. diff --git a/imgui_tables.cpp b/imgui_tables.cpp index fedf1b35..c2b219d9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2011,7 +2011,7 @@ void ImGui::TableHeader(const char* label) float w_sort_text = 0.0f; if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) { - const float ARROW_SCALE = 0.75f; + const float ARROW_SCALE = 0.65f; w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);// table->CellPadding.x); if (column->SortOrder != -1) { @@ -2036,7 +2036,7 @@ void ImGui::TableHeader(const char* label) PopStyleColor(); x += w_sort_text; } - RenderArrow(window->DrawList, ImVec2(x, y), col, column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Down : ImGuiDir_Up, ARROW_SCALE); + RenderArrow(window->DrawList, ImVec2(x, y), col, column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE); } // Handle clicking on column header to adjust Sort Order @@ -2126,7 +2126,6 @@ const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() sort_spec->ColumnUserID = column->UserID; sort_spec->ColumnIndex = (ImU8)column_n; sort_spec->SortOrder = (ImU8)column->SortOrder; - sort_spec->SortSign = (column->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; sort_spec->SortDirection = column->SortDirection; table->SortSpecs.ColumnsMask |= (ImU64)1 << column_n; } @@ -2298,7 +2297,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) // We skip saving some data in the .ini file when they are unnecessary to restore our state // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet. if (column->IndexDisplayOrder != n) - settings->SaveFlags |= ImGuiTableFlags_Reorderable;; + settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column_settings->SortOrder != -1) settings->SaveFlags |= ImGuiTableFlags_Sortable; if (column_settings->Visible != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) @@ -2446,11 +2445,13 @@ void ImGui::DebugNodeTable(ImGuiTable* table) "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Desired %d/%d\n" + "SortOrder: %d, SortDir: %s\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", n, column->IndexDisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->WidthGiven, column->WidthRequested, column->ResizeWeight, column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersDesired, + column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", column->UserID, column->Flags, (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", @@ -2465,8 +2466,11 @@ void ImGui::DebugNodeTable(ImGuiTable* table) for (int n = 0; n < settings->ColumnsCount; n++) { ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; - BulletText("Column %d Order %d SortOrder %d Visible %d UserID 0x%08X WidthOrWeight %.3f", - n, column_settings->DisplayOrder, column_settings->SortOrder, column_settings->Visible, column_settings->UserID, column_settings->WidthOrWeight); + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? column_settings->SortDirection : ImGuiSortDirection_None; + BulletText("Column %d Order %d SortOrder %d %s Visible %d UserID 0x%08X WidthOrWeight %.3f", + n, column_settings->DisplayOrder, column_settings->SortOrder, + (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", + column_settings->Visible, column_settings->UserID, column_settings->WidthOrWeight); } TreePop(); } From f130db51ae2b749219fe25ad0d8de6109cdb01cf Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 5 Mar 2020 14:31:13 +0100 Subject: [PATCH 028/144] Tables: Added TableSetColumnWidth() api variant aimed at becoming public facing. --- imgui_internal.h | 1 + imgui_tables.cpp | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/imgui_internal.h b/imgui_internal.h index efb04012..21882beb 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2185,6 +2185,7 @@ namespace ImGui IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); + IMGUI_API void TableSetColumnWidth(int column_n, float width); IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); IMGUI_API void TableDrawBorders(ImGuiTable* table); IMGUI_API void TableDrawMergeChannels(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index c2b219d9..2053c007 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -886,7 +886,7 @@ void ImGui::EndTable() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Only call EndTable() is BeginTable() returns true!"); + IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some cases, // and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) @@ -1134,6 +1134,18 @@ static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) } } +// Public wrapper +void ImGui::TableSetColumnWidth(int column_n, float width) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL); + IM_ASSERT(table->IsLayoutLocked == false); + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + TableSetColumnWidth(table, &table->Columns[column_n], width); +} + +// [Internal] void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, float column_0_width) { // Constraints From 0190b619cfc30981fe5d65b37b8dadda16c1119e Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 9 Mar 2020 11:09:54 +0100 Subject: [PATCH 029/144] Tables: Fixed demo layout when clipped. Fixed warnings. --- imgui_demo.cpp | 35 ++++++++++++++++++----------------- imgui_tables.cpp | 4 ++-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b91392ae..6c84e023 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4209,6 +4209,8 @@ static void ShowDemoWindowTables() const ImDrawList* parent_draw_list = ImGui::GetWindowDrawList(); const int parent_draw_list_draw_cmd_count = parent_draw_list->CmdBuffer.Size; + ImVec2 table_scroll_cur, table_scroll_max; // For debug display + const ImDrawList* table_draw_list = NULL; // " const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : inner_width_without_scroll; if (ImGui::BeginTable("##table", 5, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) @@ -4322,23 +4324,22 @@ static void ShowDemoWindowTables() } ImGui::PopButtonRepeat(); - const ImVec2 table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); - const ImVec2 table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY()); - const ImDrawList* table_draw_list = ImGui::GetWindowDrawList(); + table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); + table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY()); + table_draw_list = ImGui::GetWindowDrawList(); ImGui::EndTable(); - - static bool show_debug_details = false; - ImGui::Checkbox("Debug details", &show_debug_details); - if (show_debug_details) - { - ImGui::SameLine(0.0f, 0.0f); - const int table_draw_list_draw_cmd_count = table_draw_list->CmdBuffer.Size; - if (table_draw_list == parent_draw_list) - ImGui::Text(": DrawCmd: +%d (in same window)", table_draw_list_draw_cmd_count - parent_draw_list_draw_cmd_count); - else - ImGui::Text(": DrawCmd: +%d (in child window), Scroll: (%.f/%.f) (%.f/%.f)", - table_draw_list_draw_cmd_count - 1, table_scroll_cur.x, table_scroll_max.x, table_scroll_cur.y, table_scroll_max.y); - } + } + static bool show_debug_details = false; + ImGui::Checkbox("Debug details", &show_debug_details); + if (show_debug_details && table_draw_list) + { + ImGui::SameLine(0.0f, 0.0f); + const int table_draw_list_draw_cmd_count = table_draw_list->CmdBuffer.Size; + if (table_draw_list == parent_draw_list) + ImGui::Text(": DrawCmd: +%d (in same window)", table_draw_list_draw_cmd_count - parent_draw_list_draw_cmd_count); + else + ImGui::Text(": DrawCmd: +%d (in child window), Scroll: (%.f/%.f) (%.f/%.f)", + table_draw_list_draw_cmd_count - 1, table_scroll_cur.x, table_scroll_max.x, table_scroll_cur.y, table_scroll_max.y); } ImGui::TreePop(); } @@ -5076,9 +5077,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::Text("Main"); ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2053c007..d380acc9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1342,7 +1342,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) continue; ImDrawChannel* channel = &splitter->_Channels[n]; IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); - channel->_CmdBuffer[0].ClipRect = *(ImVec4*)&merge_clip_rect; + channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4(); memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); merge_channels_mask &= ~n_mask; } @@ -2478,7 +2478,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) for (int n = 0; n < settings->ColumnsCount; n++) { ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; - ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? column_settings->SortDirection : ImGuiSortDirection_None; + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; BulletText("Column %d Order %d SortOrder %d %s Visible %d UserID 0x%08X WidthOrWeight %.3f", n, column_settings->DisplayOrder, column_settings->SortOrder, (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", From 7277ab6530032bdb318acc05cc87c8091576ca47 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2020 11:49:52 +0100 Subject: [PATCH 030/144] Tables: Comments, renamed merge_set_xxx to merge_group_xxx. Removed unused array and incorrect assert, replaced with earlier correct assert. (3058) --- imgui_tables.cpp | 78 +++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d380acc9..508acbb4 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -445,9 +445,9 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) void ImGui::TableUpdateDrawChannels(ImGuiTable* table) { // Allocate draw channels. - // - We allocate them following the storage order instead of the display order so reordering won't needlessly increase overall dormant memory cost + // - We allocate them following the storage order instead of the display order so reordering columns won't needlessly increase overall dormant memory cost. // - We isolate headers draw commands in their own channels instead of just altering clip rects. This is in order to facilitate merging of draw commands. - // - After crossing FreezeRowsCount, all columns see their current draw channel increased. + // - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of draw channels. // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. // Draw channel allocation (before merging): // - NoClip --> 1+1 channels: background + foreground (same clip rect == 1 draw call) @@ -1232,13 +1232,13 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group -// based on its position (within frozen rows/columns set or not). +// based on its position (within frozen rows/columns groups or not). // At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be merged by the DrawSplitter.Merge() call. // // Column channels will not be merged into one of the 1-4 groups in the following cases: // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). -// Direct ImDrawList calls won't be noticed so if you use them make sure the ImGui:: bounds matches, by e.g. calling SetCursorScreenPos(). -// - The channel uses more than one draw command itself (we drop all our merging stuff here.. we could do better but it's going to be rare) +// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds matches, by e.g. calling SetCursorScreenPos(). +// - The channel uses more than one draw command itself. We drop all our merging stuff here.. we could do better but it's going to be rare. // // This function is particularly tricky to understand.. take a breath. void ImGui::TableDrawMergeChannels(ImGuiTable* table) @@ -1248,15 +1248,16 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) const bool is_frozen_v = (table->FreezeRowsCount > 0); const bool is_frozen_h = (table->FreezeColumnsCount > 0); - int merge_set_mask = 0; - int merge_set_channels_count[4] = { 0 }; - ImU64 merge_set_channels_mask[4] = { 0 }; - ImRect merge_set_clip_rect[4]; - for (int n = 0; n < IM_ARRAYSIZE(merge_set_clip_rect); n++) - merge_set_clip_rect[n] = ImVec4(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); - bool merge_set_all_fit_within_inner_rect = (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0; + // Track which groups (1-4) we are going to attempt to merge, and which channels goes into each group. + int merge_group_mask = 0x00; + int merge_group_channels_count[4] = { 0 }; + ImU64 merge_group_channels_mask[4] = { 0 }; + ImRect merge_group_clip_rect[4]; + for (int n = 0; n < IM_ARRAYSIZE(merge_group_clip_rect); n++) + merge_group_clip_rect[n] = ImVec4(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + bool merge_groups_all_fit_within_inner_rect = (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0; - // 1. Scan channels and take note of those who can be merged + // 1. Scan channels and take note of those which can be merged for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) @@ -1264,37 +1265,37 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) const int column_n = table->DisplayOrder[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; - const int merge_set_sub_count = is_frozen_v ? 2 : 1; - for (int merge_set_sub_n = 0; merge_set_sub_n < merge_set_sub_count; merge_set_sub_n++) + const int merge_group_sub_count = is_frozen_v ? 2 : 1; + for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++) { - const int channel_no = (merge_set_sub_n == 0) ? column->DrawChannelRowsBeforeFreeze : column->DrawChannelRowsAfterFreeze; + const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelRowsBeforeFreeze : column->DrawChannelRowsAfterFreeze; - // Don't attempt to merge if there are multiple calls within the column + // Don't attempt to merge if there are multiple draw calls within the column ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0) src_channel->_CmdBuffer.pop_back(); if (src_channel->_CmdBuffer.Size != 1) continue; - // Find out the width of this merge set and check if it will fit in our column. + // Find out the width of this merge group and check if it will fit in our column. float width_contents; - if (merge_set_sub_count == 1) // No row freeze (same as testing !is_frozen_v) + if (merge_group_sub_count == 1) // No row freeze (same as testing !is_frozen_v) width_contents = ImMax(column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed); - else if (merge_set_sub_n == 0) // Row freeze: use width before freeze + else if (merge_group_sub_n == 0) // Row freeze: use width before freeze width_contents = ImMax(column->ContentWidthRowsFrozen, column->ContentWidthHeadersUsed); - else // Row freeze: use width after freeze + else // Row freeze: use width after freeze width_contents = column->ContentWidthRowsUnfrozen; if (width_contents > column->WidthGiven && !(column->Flags & ImGuiTableColumnFlags_NoClipX)) continue; - const int dst_merge_set_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_set_sub_n : 1); - IM_ASSERT(merge_set_channels_count[dst_merge_set_n] < (int)sizeof(merge_set_channels_mask[dst_merge_set_n]) * 8); - merge_set_mask |= (1 << dst_merge_set_n); - merge_set_channels_mask[dst_merge_set_n] |= (ImU64)1 << channel_no; - merge_set_channels_count[dst_merge_set_n]++; - merge_set_clip_rect[dst_merge_set_n].Add(src_channel->_CmdBuffer[0].ClipRect); + const int dst_merge_group_idx = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); + IM_ASSERT(channel_no < 64); + merge_group_mask |= (1 << dst_merge_group_idx); + merge_group_channels_mask[dst_merge_group_idx] |= (ImU64)1 << channel_no; + merge_group_channels_count[dst_merge_group_idx]++; + merge_group_clip_rect[dst_merge_group_idx].Add(src_channel->_CmdBuffer[0].ClipRect); - // If we end with a single set and hosted by the outer window, we'll attempt to merge our draw command with + // If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command with // the existing outer window command. But we can only do so if our columns all fit within the expected clip rect, // otherwise clipping will be incorrect when ScrollX is disabled. // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column don't fit within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect @@ -1304,7 +1305,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) // 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary... #if 0 if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) - merge_set_all_fit_within_inner_rect = false; + merge_groups_all_fit_within_inner_rect = false; #endif } @@ -1312,25 +1313,26 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) column->DrawChannelCurrent = -1; } + // 2. Rewrite channel list in our preferred order - if (merge_set_mask != 0) + if (merge_group_mask != 0) { // Use shared temporary storage so the allocation gets amortized g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - 1); ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; - ImU64 remaining_mask = ((splitter->_Count < 64) ? ((ImU64)1 << splitter->_Count) - 1 : ~(ImU64)0) & ~1; - const bool may_extend_clip_rect_to_host_rect = ImIsPowerOfTwo(merge_set_mask); - for (int merge_set_n = 0; merge_set_n < 4; merge_set_n++) - if (merge_set_channels_count[merge_set_n]) + ImU64 remaining_mask = (splitter->_Count < 64) ? ((ImU64)1 << splitter->_Count) - 1 : ~(ImU64)0; + remaining_mask &= (ImU64)~1; // Background channel 0 not part of the merge (see channel allocation in TableUpdateDrawChannels) + const bool may_extend_clip_rect_to_host_rect = ImIsPowerOfTwo(merge_group_mask); + for (int merge_group_n = 0; merge_group_n < 4; merge_group_n++) + if (ImU64 merge_channels_mask = merge_group_channels_mask[merge_group_n]) { - ImU64 merge_channels_mask = merge_set_channels_mask[merge_set_n]; - ImRect merge_clip_rect = merge_set_clip_rect[merge_set_n]; + ImRect merge_clip_rect = merge_group_clip_rect[merge_group_n]; if (may_extend_clip_rect_to_host_rect) { //GetOverlayDrawList()->AddRect(table->HostClipRect.Min, table->HostClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 3.0f); //GetOverlayDrawList()->AddRect(table->InnerClipRect.Min, table->InnerClipRect.Max, IM_COL32(0, 255, 0, 200), 0.0f, ~0, 1.0f); //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 2.0f); - merge_clip_rect.Add(merge_set_all_fit_within_inner_rect ? table->HostClipRect : table->InnerClipRect); + merge_clip_rect.Add(merge_groups_all_fit_within_inner_rect ? table->HostClipRect : table->InnerClipRect); //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(0, 255, 0, 200)); } remaining_mask &= ~merge_channels_mask; @@ -1348,7 +1350,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) } } - // Append channels that we didn't reorder at the end of the list + // Append unmergeable channels that we didn't reorder at the end of the list for (int n = 0; n < splitter->_Count && remaining_mask != 0; n++) { const ImU64 n_mask = (ImU64)1 << n; From d37cef40f25da88f84920df7a6d50669c33b1dd9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2020 16:16:20 +0100 Subject: [PATCH 031/144] Tables: Tidying up TableDrawMergeChannels() with a struct. (3058) --- imgui_tables.cpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 508acbb4..b8c0fcaa 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1248,13 +1248,16 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) const bool is_frozen_v = (table->FreezeRowsCount > 0); const bool is_frozen_h = (table->FreezeColumnsCount > 0); - // Track which groups (1-4) we are going to attempt to merge, and which channels goes into each group. + // Track which groups we are going to attempt to merge, and which channels goes into each group. + struct MergeGroup + { + ImRect ClipRect; + ImU64 ChannelsMask; + int ChannelsCount; + }; int merge_group_mask = 0x00; - int merge_group_channels_count[4] = { 0 }; - ImU64 merge_group_channels_mask[4] = { 0 }; - ImRect merge_group_clip_rect[4]; - for (int n = 0; n < IM_ARRAYSIZE(merge_group_clip_rect); n++) - merge_group_clip_rect[n] = ImVec4(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + MergeGroup merge_groups[4]; + memset(merge_groups, 0, sizeof(merge_groups)); bool merge_groups_all_fit_within_inner_rect = (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0; // 1. Scan channels and take note of those which can be merged @@ -1288,12 +1291,15 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) if (width_contents > column->WidthGiven && !(column->Flags & ImGuiTableColumnFlags_NoClipX)) continue; - const int dst_merge_group_idx = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); + const int merge_group_dst_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); IM_ASSERT(channel_no < 64); - merge_group_mask |= (1 << dst_merge_group_idx); - merge_group_channels_mask[dst_merge_group_idx] |= (ImU64)1 << channel_no; - merge_group_channels_count[dst_merge_group_idx]++; - merge_group_clip_rect[dst_merge_group_idx].Add(src_channel->_CmdBuffer[0].ClipRect); + MergeGroup* merge_group = &merge_groups[merge_group_dst_n]; + if (merge_group->ChannelsCount == 0) + merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + merge_group->ChannelsMask |= (ImU64)1 << channel_no; + merge_group->ChannelsCount++; + merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); + merge_group_mask |= (1 << merge_group_dst_n); // If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command with // the existing outer window command. But we can only do so if our columns all fit within the expected clip rect, @@ -1313,7 +1319,6 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) column->DrawChannelCurrent = -1; } - // 2. Rewrite channel list in our preferred order if (merge_group_mask != 0) { @@ -1324,9 +1329,9 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) remaining_mask &= (ImU64)~1; // Background channel 0 not part of the merge (see channel allocation in TableUpdateDrawChannels) const bool may_extend_clip_rect_to_host_rect = ImIsPowerOfTwo(merge_group_mask); for (int merge_group_n = 0; merge_group_n < 4; merge_group_n++) - if (ImU64 merge_channels_mask = merge_group_channels_mask[merge_group_n]) + if (ImU64 merge_channels_mask = merge_groups[merge_group_n].ChannelsMask) { - ImRect merge_clip_rect = merge_group_clip_rect[merge_group_n]; + ImRect merge_clip_rect = merge_groups[merge_group_n].ClipRect; if (may_extend_clip_rect_to_host_rect) { //GetOverlayDrawList()->AddRect(table->HostClipRect.Min, table->HostClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 3.0f); From 054c67079ad0033666fde310fe6317686160f9f6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 13 Mar 2020 18:58:55 +0100 Subject: [PATCH 032/144] Tables: Fix scrolling with more than 32 columns (3058). Fix limit of 63 columms instead of 64. Added BitArray. --- imgui_internal.h | 19 +++++++++++++++++-- imgui_tables.cpp | 37 +++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 21882beb..3c6009d0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -476,6 +476,20 @@ inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) } } +// Helper: ImBitArray class (wrapper over ImBitArray functions) +// Store 1-bit per value. NOT CLEARED by constructor. +template +struct IMGUI_API ImBitArray +{ + ImU32 Storage[(BITCOUNT + 31) >> 5]; + ImBitArray() { } + void ClearBits() { memset(Storage, 0, sizeof(Storage)); } + bool TestBit(int n) const { IM_ASSERT(n < BITCOUNT); return ImBitArrayTestBit(Storage, n); } + void SetBit(int n) { IM_ASSERT(n < BITCOUNT); ImBitArraySetBit(Storage, n); } + void ClearBit(int n) { IM_ASSERT(n < BITCOUNT); ImBitArrayClearBit(Storage, n); } + void SetBitRange(int n1, int n2) { ImBitArraySetBitRange(Storage, n1, n2); } +}; + // Helper: ImBitVector // Store 1-bit per value. struct IMGUI_API ImBitVector @@ -1820,8 +1834,9 @@ struct ImGuiTabBar #ifdef IMGUI_HAS_TABLE -#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code -#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code +#define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. +#define IMGUI_TABLE_MAX_DRAW_CHANNELS (2 + 64 * 2) // See TableUpdateDrawChannels() // [Internal] sizeof() ~ 100 struct ImGuiTableColumn diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b8c0fcaa..ed55fb35 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -163,7 +163,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG return false; // Sanity checks - IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS && "Only 0..63 columns allowed!"); + IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!"); if (flags & ImGuiTableFlags_ScrollX) IM_ASSERT(inner_width >= 0.0f); @@ -1252,8 +1252,8 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) struct MergeGroup { ImRect ClipRect; - ImU64 ChannelsMask; int ChannelsCount; + ImBitArray ChannelsMask; }; int merge_group_mask = 0x00; MergeGroup merge_groups[4]; @@ -1292,11 +1292,11 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) continue; const int merge_group_dst_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); - IM_ASSERT(channel_no < 64); + IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); MergeGroup* merge_group = &merge_groups[merge_group_dst_n]; if (merge_group->ChannelsCount == 0) merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); - merge_group->ChannelsMask |= (ImU64)1 << channel_no; + merge_group->ChannelsMask.SetBit(channel_no); merge_group->ChannelsCount++; merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); merge_group_mask |= (1 << merge_group_dst_n); @@ -1325,12 +1325,15 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) // Use shared temporary storage so the allocation gets amortized g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - 1); ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; - ImU64 remaining_mask = (splitter->_Count < 64) ? ((ImU64)1 << splitter->_Count) - 1 : ~(ImU64)0; - remaining_mask &= (ImU64)~1; // Background channel 0 not part of the merge (see channel allocation in TableUpdateDrawChannels) + ImBitArray remaining_mask; + remaining_mask.ClearBits(); + remaining_mask.SetBitRange(1, splitter->_Count - 1); // Background channel 0 not part of the merge (see channel allocation in TableUpdateDrawChannels) + int remaining_count = splitter->_Count - 1; const bool may_extend_clip_rect_to_host_rect = ImIsPowerOfTwo(merge_group_mask); for (int merge_group_n = 0; merge_group_n < 4; merge_group_n++) - if (ImU64 merge_channels_mask = merge_groups[merge_group_n].ChannelsMask) + if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) { + MergeGroup* merge_group = &merge_groups[merge_group_n]; ImRect merge_clip_rect = merge_groups[merge_group_n].ClipRect; if (may_extend_clip_rect_to_host_rect) { @@ -1340,30 +1343,32 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) merge_clip_rect.Add(merge_groups_all_fit_within_inner_rect ? table->HostClipRect : table->InnerClipRect); //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(0, 255, 0, 200)); } - remaining_mask &= ~merge_channels_mask; - for (int n = 0; n < splitter->_Count && merge_channels_mask != 0; n++) + remaining_count -= merge_group->ChannelsCount; + for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) + remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; + for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) { // Copy + overwrite new clip rect - const ImU64 n_mask = (ImU64)1 << n; - if ((merge_channels_mask & n_mask) == 0) + if (!merge_group->ChannelsMask.TestBit(n)) continue; + merge_group->ChannelsMask.ClearBit(n); + merge_channels_count--; + ImDrawChannel* channel = &splitter->_Channels[n]; IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4(); memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); - merge_channels_mask &= ~n_mask; } } // Append unmergeable channels that we didn't reorder at the end of the list - for (int n = 0; n < splitter->_Count && remaining_mask != 0; n++) + for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) { - const ImU64 n_mask = (ImU64)1 << n; - if ((remaining_mask & n_mask) == 0) + if (!remaining_mask.TestBit(n)) continue; ImDrawChannel* channel = &splitter->_Channels[n]; memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); - remaining_mask &= ~n_mask; + remaining_count--; } IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); memcpy(splitter->_Channels.Data + 1, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - 1) * sizeof(ImDrawChannel)); From 182115409ab3d4c76ea125c538c46ed38c332162 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 18 Mar 2020 12:20:53 +0100 Subject: [PATCH 033/144] Internals: added ImSpan helper structure + 2020/10/01 stricter bound checking --- imgui_internal.h | 29 +++++++++++++++++++++++++++++ misc/natvis/imgui.natvis | 10 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/imgui_internal.h b/imgui_internal.h index 3c6009d0..12e67fda 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -249,6 +249,7 @@ namespace ImStb // - Helper: ImRect // - Helper: ImBitArray // - Helper: ImBitVector +// - Helper: ImSpan<> // - Helper: ImPool<> // - Helper: ImChunkStream<> //----------------------------------------------------------------------------- @@ -502,6 +503,34 @@ struct IMGUI_API ImBitVector void ClearBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); } }; +// Helper: ImSpan<> +// Pointing to a span of data we don't own. +template +struct ImSpan +{ + T* Data; + T* DataEnd; + + // Constructors, destructor + inline ImSpan() { Data = DataEnd = NULL; } + inline ImSpan(T* data, int size) { Data = data; DataEnd = data + size; } + inline ImSpan(T* data, T* data_end) { Data = data; DataEnd = data_end; } + + inline void set(T* data, int size) { Data = data; DataEnd = data + size; } + inline void set(T* data, T* data_end) { Data = data; DataEnd = data_end; } + inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } + inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + + inline T* begin() { return Data; } + inline const T* begin() const { return Data; } + inline T* end() { return DataEnd; } + inline const T* end() const { return DataEnd; } + + // Utilities + inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. diff --git a/misc/natvis/imgui.natvis b/misc/natvis/imgui.natvis index cc768bfb..25d72fb6 100644 --- a/misc/natvis/imgui.natvis +++ b/misc/natvis/imgui.natvis @@ -13,6 +13,16 @@ + + + {{Size={DataEnd-Data} }} + + + DataEnd-Data + Data + + + {{x={x,g} y={y,g}}} From 6dff06130998712209657d26fe612c7dfce7014b Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 18 Mar 2020 17:49:13 +0100 Subject: [PATCH 034/144] Internals: added ImSpanAllocator<> helper. --- imgui_internal.h | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/imgui_internal.h b/imgui_internal.h index 12e67fda..0530130a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -249,7 +249,7 @@ namespace ImStb // - Helper: ImRect // - Helper: ImBitArray // - Helper: ImBitVector -// - Helper: ImSpan<> +// - Helper: ImSpan<>, ImSpanAllocator<> // - Helper: ImPool<> // - Helper: ImChunkStream<> //----------------------------------------------------------------------------- @@ -531,6 +531,26 @@ struct ImSpan inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } }; +// Helper: ImSpanAllocator<> +// Facilitate storing multiple chunks into a single large block (the "arena") +template +struct ImSpanAllocator +{ + char* BasePtr; + int TotalSize; + int CurrSpan; + int Offsets[CHUNKS]; + + ImSpanAllocator() { memset(this, 0, sizeof(*this)); } + inline void ReserveBytes(int n, size_t sz) { IM_ASSERT(n == CurrSpan && n < CHUNKS); Offsets[CurrSpan++] = TotalSize; TotalSize += (int)sz; } + inline int GetArenaSizeInBytes() { return TotalSize; } + inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } + inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (void*)(BasePtr + Offsets[n]); } + inline void* GetSpanPtrEnd(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (n + 1 < CHUNKS) ? BasePtr + Offsets[n + 1] : (void*)(BasePtr + TotalSize); } + template + inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. From a956629b40843e01c59dd4b8b05c1ecee85764d0 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 18 Mar 2020 18:17:22 +0100 Subject: [PATCH 035/144] Tables: Using same allocation for our Columns and DisplayOrder array. Mostly designed to facilitate adding new arrays. --- imgui_internal.h | 7 ++++--- imgui_tables.cpp | 35 ++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 0530130a..a8055b26 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1946,8 +1946,9 @@ struct ImGuiTable { ImGuiID ID; ImGuiTableFlags Flags; - ImVector Columns; - ImVector DisplayOrder; // Store display order of columns (when not reordered, the values are 0...Count-1) + ImVector RawData; + ImSpan Columns; // Point within RawData[] + ImSpan DisplayOrder; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map ImU64 VisibleMaskByIndex; // Visible (== Active and not Clipped) @@ -1992,7 +1993,7 @@ struct ImGuiTable ImGuiWindow* OuterWindow; // Parent window for the table ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names - ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (could be stored outside?) + ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (FIXME: could be stored outside, worst case we need 1 splitter per recursing table) ImVector SortSpecsData; // FIXME-OPT: Fixed-size array / small-vector pattern, optimize for single sort spec ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() ImS8 SortSpecsCount; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index ed55fb35..fbb6de8b 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -288,26 +288,27 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if ((table_last_flags & ImGuiTableFlags_Reorderable) && !(flags & ImGuiTableFlags_Reorderable)) table->IsResetDisplayOrderRequest = true; - // Clear data if columns count changed - if (table->Columns.Size != 0 && table->Columns.Size != columns_count) + // Setup default columns state. Clear data if columns count changed + const int stored_size = table->Columns.size(); + if (stored_size != 0 && stored_size != columns_count) + table->RawData.resize(0); + if (table->RawData.Size == 0) { - table->Columns.resize(0); - table->DisplayOrder.resize(0); - } + // Allocate single buffer for our arrays + ImSpanAllocator<2> span_allocator; + span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn)); + span_allocator.ReserveBytes(1, columns_count * sizeof(ImS8)); + table->RawData.resize(span_allocator.GetArenaSizeInBytes()); + span_allocator.SetArenaBasePtr(table->RawData.Data); + span_allocator.GetSpan(0, &table->Columns); + span_allocator.GetSpan(1, &table->DisplayOrder); - // Setup default columns state - if (table->Columns.Size == 0) - { - table->IsInitializing = table->IsSettingsRequestLoad = table->IsSortSpecsDirty = true; - table->Columns.reserve(columns_count); - table->DisplayOrder.reserve(columns_count); for (int n = 0; n < columns_count; n++) { - ImGuiTableColumn column; - column.IndexDisplayOrder = (ImS8)n; - table->Columns.push_back(column); - table->DisplayOrder.push_back(column.IndexDisplayOrder); + table->Columns[n] = ImGuiTableColumn(); + table->Columns[n].IndexDisplayOrder = table->DisplayOrder[n] = (ImS8)n; } + table->IsInitializing = table->IsSettingsRequestLoad = table->IsSortSpecsDirty = true; } // Load settings @@ -378,7 +379,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) if (table->IsResetDisplayOrderRequest) { for (int n = 0; n < table->ColumnsCount; n++) - table->DisplayOrder[n] = table->Columns[n].IndexDisplayOrder = (ImU8)n; + table->DisplayOrder[n] = table->Columns[n].IndexDisplayOrder = (ImS8)n; table->IsResetDisplayOrderRequest = false; table->IsSettingsDirty = true; } @@ -2376,7 +2377,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) // FIXME-TABLE: Need to validate .ini data for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->DisplayOrder[table->Columns[column_n].IndexDisplayOrder] = (ImU8)column_n; + table->DisplayOrder[table->Columns[column_n].IndexDisplayOrder] = (ImS8)column_n; } void* ImGui::TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) From 5ffc9e084618770b6f6afc82309af311af2d4eb6 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 18 Mar 2020 18:20:17 +0100 Subject: [PATCH 036/144] Tables: Renaming Table's DisplayOrder[] -> DisplayOrderToIndex[], Columns's IndexDisplayOrder -> DisplayOrder --- imgui_internal.h | 6 +++--- imgui_tables.cpp | 52 ++++++++++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index a8055b26..789e71a1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1914,7 +1914,7 @@ struct ImGuiTableColumn bool NextIsActive; bool IsClipped; // Set when not overlapping the host window clipping rectangle. We don't use the opposite "!Visible" name because Clipped can be altered by events. bool SkipItems; - ImS8 IndexDisplayOrder; // Index within DisplayOrder[] (column may be reordered by users) + ImS8 DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) ImS8 IndexWithinActiveSet; // Index within active set (<= IndexOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] ImS8 DrawChannelRowsBeforeFreeze; @@ -1932,7 +1932,7 @@ struct ImGuiTableColumn ResizeWeight = WidthRequested = WidthGiven = -1.0f; NameOffset = -1; IsActive = NextIsActive = true; - IndexDisplayOrder = IndexWithinActiveSet = -1; + DisplayOrder = IndexWithinActiveSet = -1; DrawChannelCurrent = DrawChannelRowsBeforeFreeze = DrawChannelRowsAfterFreeze = -1; PrevActiveColumn = NextActiveColumn = -1; AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames @@ -1948,7 +1948,7 @@ struct ImGuiTable ImGuiTableFlags Flags; ImVector RawData; ImSpan Columns; // Point within RawData[] - ImSpan DisplayOrder; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) + ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map ImU64 VisibleMaskByIndex; // Visible (== Active and not Clipped) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index fbb6de8b..f79bc73d 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -301,12 +301,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->RawData.resize(span_allocator.GetArenaSizeInBytes()); span_allocator.SetArenaBasePtr(table->RawData.Data); span_allocator.GetSpan(0, &table->Columns); - span_allocator.GetSpan(1, &table->DisplayOrder); + span_allocator.GetSpan(1, &table->DisplayOrderToIndex); for (int n = 0; n < columns_count; n++) { table->Columns[n] = ImGuiTableColumn(); - table->Columns[n].IndexDisplayOrder = table->DisplayOrder[n] = (ImS8)n; + table->Columns[n].DisplayOrder = table->DisplayOrderToIndex[n] = (ImS8)n; } table->IsInitializing = table->IsSettingsRequestLoad = table->IsSortSpecsDirty = true; } @@ -360,16 +360,16 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevActiveColumn : src_column->NextActiveColumn]; IM_UNUSED(dst_column); - const int src_order = src_column->IndexDisplayOrder; - const int dst_order = dst_column->IndexDisplayOrder; - src_column->IndexDisplayOrder = (ImS8)dst_order; + const int src_order = src_column->DisplayOrder; + const int dst_order = dst_column->DisplayOrder; + src_column->DisplayOrder = (ImS8)dst_order; for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) - table->Columns[table->DisplayOrder[order_n]].IndexDisplayOrder -= (ImS8)reorder_dir; - IM_ASSERT(dst_column->IndexDisplayOrder == dst_order - reorder_dir); + table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImS8)reorder_dir; + IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], rebuild the later from the former. for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->DisplayOrder[table->Columns[column_n].IndexDisplayOrder] = (ImS8)column_n; + table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; table->ReorderColumnDir = 0; table->IsSettingsDirty = true; } @@ -379,7 +379,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) if (table->IsResetDisplayOrderRequest) { for (int n = 0; n < table->ColumnsCount; n++) - table->DisplayOrder[n] = table->Columns[n].IndexDisplayOrder = (ImS8)n; + table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImS8)n; table->IsResetDisplayOrderRequest = false; table->IsSettingsDirty = true; } @@ -391,7 +391,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) bool want_column_auto_fit = false; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - const int column_n = table->DisplayOrder[order_n]; + const int column_n = table->DisplayOrderToIndex[order_n]; if (column_n != order_n) table->IsDefaultDisplayOrder = false; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -411,7 +411,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) want_column_auto_fit = true; ImU64 index_mask = (ImU64)1 << column_n; - ImU64 display_order_mask = (ImU64)1 << column->IndexDisplayOrder; + ImU64 display_order_mask = (ImU64)1 << column->DisplayOrder; if (column->IsActive) { column->PrevActiveColumn = column->NextActiveColumn = -1; @@ -432,7 +432,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->ActiveMaskByIndex &= ~index_mask; table->ActiveMaskByDisplayOrder &= ~display_order_mask; } - IM_ASSERT(column->IndexWithinActiveSet <= column->IndexDisplayOrder); + IM_ASSERT(column->IndexWithinActiveSet <= column->DisplayOrder); } table->VisibleMaskByIndex = table->ActiveMaskByIndex; // Columns will be masked out by TableUpdateLayout() when Clipped table->RightMostActiveColumn = (ImS8)(last_active_column ? table->Columns.index_from_ptr(last_active_column) : -1); @@ -549,7 +549,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; - const int column_n = table->DisplayOrder[order_n]; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; // Adjust flags: default width mode + weighted columns are not allowed when auto extending @@ -592,7 +592,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ResizeWeight = 1.0f; total_weights += column->ResizeWeight; if (table->LeftMostStretchedColumnDisplayOrder == -1) - table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->IndexDisplayOrder; + table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->DisplayOrder; } } @@ -615,7 +615,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; - ImGuiTableColumn* column = &table->Columns[table->DisplayOrder[order_n]]; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; // Allocate width for stretched/weighted columns if (column->Flags & ImGuiTableColumnFlags_WidthStretch) @@ -678,7 +678,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; - ImGuiTableColumn* column = &table->Columns[table->DisplayOrder[order_n]]; + ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; column->WidthRequested += 1.0f; @@ -692,7 +692,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) ImRect host_clip_rect = table->InnerClipRect; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - const int column_n = table->DisplayOrder[order_n]; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == active_n) @@ -844,7 +844,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; - const int column_n = table->DisplayOrder[order_n]; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; // Detect hovered column: @@ -1049,7 +1049,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; - const int column_n = table->DisplayOrder[order_n]; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; const bool is_hovered = (table->HoveredColumnBorder == column_n); const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceNo); @@ -1190,7 +1190,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to // ensure that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others. if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) - if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->IndexDisplayOrder) + if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->DisplayOrder) { // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) float column_1_width = ImMax(column_1->WidthRequested - (column_0_width - column_0->WidthRequested), min_width); @@ -1266,7 +1266,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; - const int column_n = table->DisplayOrder[order_n]; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; const int merge_group_sub_count = is_frozen_v ? 2 : 1; @@ -2314,14 +2314,14 @@ void ImGui::TableSaveSettings(ImGuiTable* table) { //column_settings->WidthOrWeight = column->WidthRequested; // FIXME-WIP column_settings->Index = (ImS8)n; - column_settings->DisplayOrder = column->IndexDisplayOrder; + column_settings->DisplayOrder = column->DisplayOrder; column_settings->SortOrder = column->SortOrder; column_settings->SortDirection = column->SortDirection; column_settings->Visible = column->IsActive; // We skip saving some data in the .ini file when they are unnecessary to restore our state // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet. - if (column->IndexDisplayOrder != n) + if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column_settings->SortOrder != -1) settings->SaveFlags |= ImGuiTableFlags_Sortable; @@ -2366,7 +2366,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; //column->WidthRequested = column_settings->WidthOrWeight; // FIXME-WIP if (column_settings->DisplayOrder != -1) - column->IndexDisplayOrder = column_settings->DisplayOrder; + column->DisplayOrder = column_settings->DisplayOrder; if (column_settings->SortOrder != -1) { column->SortOrder = column_settings->SortOrder; @@ -2377,7 +2377,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) // FIXME-TABLE: Need to validate .ini data for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->DisplayOrder[table->Columns[column_n].IndexDisplayOrder] = (ImS8)column_n; + table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; } void* ImGui::TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) @@ -2472,7 +2472,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Desired %d/%d\n" "SortOrder: %d, SortDir: %s\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", - n, column->IndexDisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, + n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->WidthGiven, column->WidthRequested, column->ResizeWeight, column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersDesired, From b7fa96679e96e46c968df66f4204668b2aec2d0d Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 19 Mar 2020 14:55:42 +0100 Subject: [PATCH 037/144] Tables: Locking IndentX per-row so multiple columns with IndentEnabled don't get indent shearing. --- imgui_internal.h | 8 ++++---- imgui_tables.cpp | 26 ++++++++++++++------------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 789e71a1..bb4e4b2e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1941,7 +1941,6 @@ struct ImGuiTableColumn } }; -// FIXME-OPT: Since CountColumns is invariant, we could use a single alloc for ImGuiTable + the three vectors it is carrying. struct ImGuiTable { ImGuiID ID; @@ -1965,6 +1964,7 @@ struct ImGuiTable float RowPosY2; float RowMinHeight; // Height submitted to TableNextRow() float RowTextBaseline; + float RowIndentOffsetX; ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ ImGuiTableRowFlags LastRowFlags : 16; int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper) @@ -2259,11 +2259,11 @@ namespace ImGui IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); IMGUI_API void TableBeginRow(ImGuiTable* table); IMGUI_API void TableEndRow(ImGuiTable* table); - IMGUI_API void TableBeginCell(ImGuiTable* table, int column_no); + IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API ImRect TableGetCellRect(); - IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_no); - IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_no); + IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_n); + IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); IMGUI_API void TableLoadSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f79bc73d..123b886d 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1492,6 +1492,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) table->RowPosY1 = table->RowPosY2 = next_y1; table->RowTextBaseline = 0.0f; + table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent window->DC.PrevLineTextBaseOffset = 0.0f; window->DC.CursorMaxPos.y = next_y1; @@ -1611,25 +1612,26 @@ void ImGui::TableEndRow(ImGuiTable* table) table->IsInsideRow = false; } -// [Internal] Called by TableNextRow()TableNextCell()! -// This is called a lot, so we need to be mindful of unnecessary overhead. +// [Internal] Called by TableNextCell()! +// This is called very frequently, so we need to be mindful of unnecessary overhead. // FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. -void ImGui::TableBeginCell(ImGuiTable* table, int column_no) +void ImGui::TableBeginCell(ImGuiTable* table, int column_n) { - table->CurrentColumn = column_no; - ImGuiTableColumn* column = &table->Columns[column_no]; + table->CurrentColumn = column_n; + ImGuiTableColumn* column = &table->Columns[column_n]; ImGuiWindow* window = table->InnerWindow; + // Start position is roughly ~~ CellRect.Min + CellPadding + Indent float start_x = (table->RowFlags & ImGuiTableRowFlags_Headers) ? column->StartXHeaders : column->StartXRows; if (column->Flags & ImGuiTableColumnFlags_IndentEnable) - start_x += window->DC.Indent.x - table->HostIndentX; + start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row. - window->DC.LastItemId = 0; window->DC.CursorPos.x = start_x; window->DC.CursorPos.y = table->RowPosY1 + table->CellPaddingY; window->DC.CursorMaxPos.x = window->DC.CursorPos.x; window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; + window->DC.LastItemId = 0; window->WorkRect.Min.y = window->DC.CursorPos.y; window->WorkRect.Min.x = column->MinX + table->CellPaddingX1; @@ -1653,7 +1655,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_no) //window->DrawList->UpdateClipRect(); window->DrawList->PopClipRect(); window->DrawList->PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); - //IMGUI_DEBUG_LOG("%d (%.0f,%.0f)(%.0f,%.0f)\n", column_no, column->ClipRect.Min.x, column->ClipRect.Min.y, column->ClipRect.Max.x, column->ClipRect.Max.y); + //IMGUI_DEBUG_LOG("%d (%.0f,%.0f)(%.0f,%.0f)\n", column_n, column->ClipRect.Min.x, column->ClipRect.Min.y, column->ClipRect.Max.x, column->ClipRect.Max.y); window->ClipRect = window->DrawList->_ClipRectStack.back(); } } @@ -1759,19 +1761,19 @@ ImRect ImGui::TableGetCellRect() return ImRect(column->MinX, table->RowPosY1, column->MaxX, table->RowPosY2); } -const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_no) +const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_n) { - ImGuiTableColumn* column = &table->Columns[column_no]; + ImGuiTableColumn* column = &table->Columns[column_n]; if (column->NameOffset == -1) return NULL; return &table->ColumnsNames.Buf[column->NameOffset]; } -void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_no) +void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_n) { // Disable clipping then auto-fit, will take 2 frames // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) - ImGuiTableColumn* column = &table->Columns[column_no]; + ImGuiTableColumn* column = &table->Columns[column_n]; column->CannotSkipItemsQueue = (1 << 0); column->AutoFitQueue = (1 << 1); } From 104b11051ff912ca26524e7043218a63d954ba39 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 20 Mar 2020 18:53:44 +0100 Subject: [PATCH 038/144] Tables: Added ImGuiTableColumnFlags_NoReorder. --- imgui.h | 1 + imgui_demo.cpp | 7 ++++++- imgui_internal.h | 7 ++++--- imgui_tables.cpp | 28 +++++++++++++++++----------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/imgui.h b/imgui.h index cc380ea0..998909c8 100644 --- a/imgui.h +++ b/imgui.h @@ -1081,6 +1081,7 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_PreferSortDescending = 1 << 13, // Make the initial sort direction Descending when first sorting on this column. ImGuiTableColumnFlags_IndentEnable = 1 << 14, // Use current Indent value when entering cell (default for 1st column). ImGuiTableColumnFlags_IndentDisable = 1 << 15, // Ignore current Indent value when entering cell (default for columns after the 1st one). Indentation changes _within_ the cell will still be honored. + ImGuiTableColumnFlags_NoReorder = 1 << 16, // Disable reordering this column, this will also prevent other columns from crossing over this column. // [Internal] Combinations and masks ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthAlwaysAutoResize, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 6c84e023..93168cbd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3672,6 +3672,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("_NoResize", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoResize); ImGui::CheckboxFlags("_NoClipX", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoClipX); ImGui::CheckboxFlags("_NoHide", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoHide); + ImGui::CheckboxFlags("_NoReorder", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoReorder); ImGui::CheckboxFlags("_DefaultSort", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_DefaultSort); ImGui::CheckboxFlags("_DefaultHide", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_DefaultHide); ImGui::CheckboxFlags("_NoSort", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSort); @@ -4213,7 +4214,7 @@ static void ShowDemoWindowTables() const ImDrawList* table_draw_list = NULL; // " const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : inner_width_without_scroll; - if (ImGui::BeginTable("##table", 5, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) + if (ImGui::BeginTable("##table", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) { // Declare columns // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. @@ -4223,6 +4224,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); ImGui::TableSetupColumn("Quantity Long Label", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 1.0f, MyItemColumnID_Quantity);// , ImGuiTableColumnFlags_None | ImGuiTableColumnFlags_WidthAlwaysAutoResize); ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 1.0f, MyItemColumnID_Description);// , ImGuiTableColumnFlags_WidthAlwaysAutoResize); + ImGui::TableSetupColumn("Hidden", ImGuiTableColumnFlags_DefaultHide | ImGuiTableColumnFlags_NoSort); // Sort our data if sort specs have been changed! const ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); @@ -4319,6 +4321,9 @@ static void ShowDemoWindowTables() else ImGui::Text("Lorem ipsum dolor sit amet"); + ImGui::TableNextCell(); + ImGui::Text("1234"); + ImGui::PopID(); } } diff --git a/imgui_internal.h b/imgui_internal.h index bb4e4b2e..b75119ec 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1888,6 +1888,7 @@ struct ImGuiTabBar #define IMGUI_TABLE_MAX_DRAW_CHANNELS (2 + 64 * 2) // See TableUpdateDrawChannels() // [Internal] sizeof() ~ 100 +// We use the terminology "Active" to refer to a column that is not Hidden by user or programmatically. We don't use the term "Visible" because it is ambiguous since an Active column can be non-visible because of scrolling. struct ImGuiTableColumn { ImRect ClipRect; // Clipping rectangle for the column @@ -1911,11 +1912,11 @@ struct ImGuiTableColumn ImS16 ContentWidthHeadersDesired; ImS16 NameOffset; // Offset into parent ColumnsNames[] bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. - bool NextIsActive; + bool IsActiveNextFrame; bool IsClipped; // Set when not overlapping the host window clipping rectangle. We don't use the opposite "!Visible" name because Clipped can be altered by events. bool SkipItems; ImS8 DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) - ImS8 IndexWithinActiveSet; // Index within active set (<= IndexOrder) + ImS8 IndexWithinActiveSet; // Index within active/visible set (<= IndexToDisplayOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] ImS8 DrawChannelRowsBeforeFreeze; ImS8 DrawChannelRowsAfterFreeze; @@ -1931,7 +1932,7 @@ struct ImGuiTableColumn memset(this, 0, sizeof(*this)); ResizeWeight = WidthRequested = WidthGiven = -1.0f; NameOffset = -1; - IsActive = NextIsActive = true; + IsActive = IsActiveNextFrame = true; DisplayOrder = IndexWithinActiveSet = -1; DrawChannelCurrent = DrawChannelRowsBeforeFreeze = DrawChannelRowsAfterFreeze = -1; PrevActiveColumn = NextActiveColumn = -1; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 123b886d..5a9f7627 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -397,10 +397,10 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; column->NameOffset = -1; if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) - column->NextIsActive = true; - if (column->IsActive != column->NextIsActive) + column->IsActiveNextFrame = true; + if (column->IsActive != column->IsActiveNextFrame) { - column->IsActive = column->NextIsActive; + column->IsActive = column->IsActiveNextFrame; table->IsSettingsDirty = true; if (!column->IsActive && column->SortOrder != -1) table->IsSortSpecsDirty = true; @@ -1432,7 +1432,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, { // Init default visibility/sort state if (flags & ImGuiTableColumnFlags_DefaultHide) - column->IsActive = column->NextIsActive = false; + column->IsActive = column->IsActiveNextFrame = false; if (flags & ImGuiTableColumnFlags_DefaultSort) { column->SortOrder = 0; // Multiple columns using _DefaultSort will be reordered when building the sort specs. @@ -1859,7 +1859,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) if (column->IsActive && table->ColumnsActiveCount <= 1) menu_item_active = false; if (MenuItem(name, NULL, column->IsActive, menu_item_active)) - column->NextIsActive = !column->IsActive; + column->IsActiveNextFrame = !column->IsActive; } PopItemFlag(); } @@ -2018,19 +2018,25 @@ void ImGui::TableHeader(const char* label) table->HeldHeaderColumn = (ImS8)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; - // Drag and drop: re-order columns. Frozen columns are not reorderable. + // Drag and drop to re-order columns. // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) { // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x table->ReorderColumn = (ImS8)column_n; table->InstanceInteracted = table->InstanceNo; + + // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) - if (column->PrevActiveColumn != -1 && (column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (table->Columns[column->PrevActiveColumn].IndexWithinActiveSet < table->FreezeColumnsRequest)) - table->ReorderColumnDir = -1; + if (ImGuiTableColumn* prev_column = (column->PrevActiveColumn != -1) ? &table->Columns[column->PrevActiveColumn] : NULL) + if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinActiveSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = -1; if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) - if (column->NextActiveColumn != -1 && (column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (table->Columns[column->NextActiveColumn].IndexWithinActiveSet < table->FreezeColumnsRequest)) - table->ReorderColumnDir = +1; + if (ImGuiTableColumn* next_column = (column->NextActiveColumn != -1) ? &table->Columns[column->NextActiveColumn] : NULL) + if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (next_column->IndexWithinActiveSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = +1; } // Sort order arrow @@ -2374,7 +2380,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } - column->IsActive = column->NextIsActive = column_settings->Visible; + column->IsActive = column->IsActiveNextFrame = column_settings->Visible; } // FIXME-TABLE: Need to validate .ini data From e60b5a3f75a7b69e0308a778443dbee7efdcce1b Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 6 Apr 2020 17:20:17 +0200 Subject: [PATCH 039/144] Tables: Internals: Added TableGetColumnResizeID(), renamed InstanceNo > InstanceCurrent. --- imgui_internal.h | 5 +++-- imgui_tables.cpp | 34 +++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index b75119ec..12a5f983 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1959,7 +1959,7 @@ struct ImGuiTable int ColumnsActiveCount; // Number of non-hidden columns (<= ColumnsCount) int CurrentColumn; int CurrentRow; - ImS16 InstanceNo; // Count of BeginTable() calls with same ID in the same frame (generally 0) + ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched. ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with float RowPosY1; float RowPosY2; @@ -2001,7 +2001,7 @@ struct ImGuiTable ImS8 DeclColumnsCount; // Count calls to TableSetupColumn() ImS8 HoveredColumnBody; // [DEBUG] Unlike HoveredColumnBorder this doesn't fulfill all Hovering rules properly. Used for debugging/tools for now. ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). - ImS8 ResizedColumn; // Index of column being resized. Reset by InstanceNo==0. + ImS8 ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. ImS8 LastResizedColumn; // Index of column being resized from previous frame. ImS8 HeldHeaderColumn; // Index of column header being held. ImS8 ReorderColumn; // Index of column being reordered. (not cleared) @@ -2264,6 +2264,7 @@ namespace ImGui IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API ImRect TableGetCellRect(); IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_n); + IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 5a9f7627..9d7acc24 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -186,7 +186,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Acquire storage for the table ImGuiTable* table = g.Tables.GetOrAddByKey(id); const ImGuiTableFlags table_last_flags = table->Flags; - const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceNo + 1; + const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; const ImGuiID instance_id = id + instance_no; if (instance_no > 0) IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); @@ -194,7 +194,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Initialize table->ID = id; table->Flags = flags; - table->InstanceNo = (ImS16)instance_no; + table->InstanceCurrent = (ImS16)instance_no; table->LastFrameActive = g.FrameCount; table->OuterWindow = table->InnerWindow = outer_window; table->ColumnsCount = columns_count; @@ -332,7 +332,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) // (We process this at the first TableBegin of the frame) // FIXME-TABLE: Preserve contents width _while resizing down_ until releasing. // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling. - if (table->InstanceNo == 0) + if (table->InstanceCurrent == 0) { if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) TableSetColumnWidth(table, &table->Columns[table->ResizedColumn], table->ResizedColumnNextWidth); @@ -343,7 +343,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) // Handle reordering request // Note: we don't clear ReorderColumn after handling the request. - if (table->InstanceNo == 0) + if (table->InstanceCurrent == 0) { if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) table->ReorderColumn = -1; @@ -799,7 +799,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsUsingHeaders = false; // Context menu - if (table->IsContextPopupOpen && table->InstanceNo == table->InstanceInteracted) + if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted) { if (BeginPopup("##TableContextMenu")) { @@ -856,7 +856,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) continue; - ImGuiID column_id = table->ID + (table->InstanceNo * table->ColumnsCount) + column_n; + ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, hit_y2); //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); KeepAliveID(column_id); @@ -873,7 +873,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if (held) { table->ResizedColumn = (ImS8)column_n; - table->InstanceInteracted = table->InstanceNo; + table->InstanceInteracted = table->InstanceCurrent; } if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) { @@ -963,7 +963,7 @@ void ImGui::EndTable() { inner_window->Scroll.x = 0.0f; } - else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceNo) + else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceCurrent) { ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn]; if (column->MaxX < table->InnerClipRect.Min.x) @@ -1052,7 +1052,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; const bool is_hovered = (table->HoveredColumnBorder == column_n); - const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceNo); + const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); if (column->NextActiveColumn == -1 && !is_resizable) @@ -1769,6 +1769,14 @@ const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_n) return &table->ColumnsNames.Buf[column->NameOffset]; } +// Return the resizing ID for the right-side of the given column. +ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no) +{ + IM_ASSERT(column_n < table->ColumnsCount); + ImGuiID id = table->ID + (instance_no * table->ColumnsCount) + column_n; + return id; +} + void ImGui::TableSetColumnAutofit(ImGuiTable* table, int column_n) { // Disable clipping then auto-fit, will take 2 frames @@ -1917,7 +1925,7 @@ void ImGui::TableAutoHeaders() //if (g.IO.KeyCtrl) { static char buf[32]; name = buf; ImGuiTableColumn* c = &table->Columns[column_n]; if (c->Flags & ImGuiTableColumnFlags_WidthStretch) ImFormatString(buf, 32, "%.3f>%.1f", c->ResizeWeight, c->WidthGiven); else ImFormatString(buf, 32, "%.1f", c->WidthGiven); } // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) - PushID(table->InstanceNo * table->ColumnsCount + column_n); + PushID(table->InstanceCurrent * table->ColumnsCount + column_n); TableHeader(name); PopID(); @@ -1964,7 +1972,7 @@ void ImGui::TableAutoHeaders() { table->IsContextPopupOpen = true; table->ContextPopupColumn = (ImS8)open_context_popup; - table->InstanceInteracted = table->InstanceNo; + table->InstanceInteracted = table->InstanceCurrent; OpenPopup("##TableContextMenu"); } } @@ -2011,7 +2019,7 @@ void ImGui::TableHeader(const char* label) //window->DC.CursorPos.x = column->MinX + table->CellPadding.x; // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) - const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceNo); + const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld | ImGuiSelectableFlags_DontClosePopups, ImVec2(0.0f, label_height)); const bool held = IsItemActive(); if (held) @@ -2024,7 +2032,7 @@ void ImGui::TableHeader(const char* label) { // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x table->ReorderColumn = (ImS8)column_n; - table->InstanceInteracted = table->InstanceNo; + table->InstanceInteracted = table->InstanceCurrent; // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) From 8eb1c925f068098a754e2cbe73824ad59efe80fc Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 15 Apr 2020 11:05:50 +0200 Subject: [PATCH 040/144] Tables: Internals: Added FindTableByID(), removing trailing spaces. # Conflicts: # imgui_internal.h --- imgui.h | 4 ++-- imgui_internal.h | 7 ++++--- imgui_tables.cpp | 8 +++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/imgui.h b/imgui.h index 998909c8..f559da62 100644 --- a/imgui.h +++ b/imgui.h @@ -176,7 +176,7 @@ typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: f typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() typedef int ImGuiTabItemFlags; // -> enum ImGuiTabItemFlags_ // Flags: for BeginTabItem() typedef int ImGuiTableFlags; // -> enum ImGuiTableFlags_ // Flags: For BeginTable() -typedef int ImGuiTableColumnFlags; // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn() +typedef int ImGuiTableColumnFlags; // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn() typedef int ImGuiTableRowFlags; // -> enum ImGuiTableRowFlags_ // Flags: For TableNextRow() typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader() typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin(), BeginChild() @@ -672,7 +672,7 @@ namespace ImGui // - If you are using tables as a sort of grid, populating every columns with the same type of contents, // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). // - See Demo->Tables for details. - // - See ImGuiTableFlags_ enums for a description of available flags. + // - See ImGuiTableFlags_ enums for a description of available flags. #define IMGUI_HAS_TABLE 1 IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! diff --git a/imgui_internal.h b/imgui_internal.h index 12a5f983..1e7e3075 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -547,7 +547,7 @@ struct ImSpanAllocator inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (void*)(BasePtr + Offsets[n]); } inline void* GetSpanPtrEnd(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (n + 1 < CHUNKS) ? BasePtr + Offsets[n + 1] : (void*)(BasePtr + TotalSize); } - template + template inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } }; @@ -1901,7 +1901,7 @@ struct ImGuiTableColumn float WidthRequested; // Master width data when !(Flags & _WidthStretch) float WidthGiven; // == (MaxX - MinX). FIXME-TABLE: Store all persistent width in multiple of FontSize? float StartXRows; // Start position for the frame, currently ~(MinX + CellPaddingX) - float StartXHeaders; + float StartXHeaders; float ContentMaxPosRowsFrozen; // Submitted contents absolute maximum position, from which we can infer width. float ContentMaxPosRowsUnfrozen; // (kept as float because we need to manipulate those between each cell change) float ContentMaxPosHeadersUsed; @@ -2003,7 +2003,7 @@ struct ImGuiTable ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). ImS8 ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. ImS8 LastResizedColumn; // Index of column being resized from previous frame. - ImS8 HeldHeaderColumn; // Index of column header being held. + ImS8 HeldHeaderColumn; // Index of column header being held. ImS8 ReorderColumn; // Index of column being reordered. (not cleared) ImS8 ReorderColumnDir; // -1 or +1 ImS8 RightMostActiveColumn; // Index of right-most non-hidden column. @@ -2246,6 +2246,7 @@ namespace ImGui IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); // Tables + IMGUI_API ImGuiTable* FindTableByID(ImGuiID id); IMGUI_API bool BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void TableBeginUpdateColumns(ImGuiTable* table); IMGUI_API void TableUpdateDrawChannels(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 9d7acc24..0e37a2e1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -127,6 +127,12 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) return flags; } +ImGuiTable* ImGui::FindTableByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.Tables.GetByKey(id); +} + // About 'outer_size': // The meaning of outer_size needs to differ slightly depending of if we are using ScrollX/ScrollY flags. // With ScrollX/ScrollY: using a child window for scrolling: @@ -2137,7 +2143,7 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click table->IsSortSpecsDirty = true; } -// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) +// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) // You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() From b7ff85d9ad9872ec3b16ce1f6c2f57c16ef3703f Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 7 May 2020 23:52:17 +0200 Subject: [PATCH 041/144] Tables: Browse settings list in Metrics (outside of Table entry). --- imgui.cpp | 5 +++-- imgui_internal.h | 1 + imgui_tables.cpp | 37 +++++++++++++++++++++---------------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c8023fdb..47c0c4d3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10689,8 +10689,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) #ifdef IMGUI_HAS_TABLE if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) { - //for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) - // DebugNodeTableSettings(settings); + for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + DebugNodeTableSettings(settings); TreePop(); } #endif // #ifdef IMGUI_HAS_TABLE @@ -11071,6 +11071,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImGuiWindow*, const ImDrawLis void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeTable(ImGuiTable*) {} +void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} diff --git a/imgui_internal.h b/imgui_internal.h index 1e7e3075..62385a77 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2394,6 +2394,7 @@ namespace ImGui IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); + IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 0e37a2e1..025f144e 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2505,25 +2505,30 @@ void ImGui::DebugNodeTable(ImGuiTable* table) (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "", (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); } - ImGuiTableSettings* settings = TableFindSettings(table); - if (settings && TreeNode("Settings")) - { - BulletText("SaveFlags: 0x%08X", settings->SaveFlags); - BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); - for (int n = 0; n < settings->ColumnsCount; n++) - { - ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; - ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; - BulletText("Column %d Order %d SortOrder %d %s Visible %d UserID 0x%08X WidthOrWeight %.3f", - n, column_settings->DisplayOrder, column_settings->SortOrder, - (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", - column_settings->Visible, column_settings->UserID, column_settings->WidthOrWeight); - } - TreePop(); - } + if (ImGuiTableSettings* settings = TableFindSettings(table)) + DebugNodeTableSettings(settings); TreePop(); } } + +void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) +{ + if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount)) + return; + BulletText("SaveFlags: 0x%08X", settings->SaveFlags); + BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); + for (int n = 0; n < settings->ColumnsCount; n++) + { + ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n]; + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; + BulletText("Column %d Order %d SortOrder %d %s Visible %d UserID 0x%08X WidthOrWeight %.3f", + n, column_settings->DisplayOrder, column_settings->SortOrder, + (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", + column_settings->Visible, column_settings->UserID, column_settings->WidthOrWeight); + } + TreePop(); +} + #endif // #ifndef IMGUI_DISABLE_METRICS_WINDOW //------------------------------------------------------------------------- From 9f43aae226c352226abc6c121d96f96dafc946a6 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 12 May 2020 17:23:10 +0200 Subject: [PATCH 042/144] Tables: Calculating ideal total width, some renaming, comments. Clarify that inner_width is unused with ScrollX=0. Clip many comments to 120 columns. --- imgui.cpp | 4 +- imgui_demo.cpp | 12 +-- imgui_internal.h | 13 +-- imgui_tables.cpp | 272 +++++++++++++++++++++++++++-------------------- 4 files changed, 168 insertions(+), 133 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 47c0c4d3..55c67bbb 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10515,7 +10515,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } else if (rect_type == TRT_ColumnsContentHeadersUsed) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate - else if (rect_type == TRT_ColumnsContentHeadersIdeal) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersDesired, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " + else if (rect_type == TRT_ColumnsContentHeadersIdeal) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " else if (rect_type == TRT_ColumnsContentRowsFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthRowsFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " else if (rect_type == TRT_ColumnsContentRowsUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->MinX + c->ContentWidthRowsUnfrozen, table->InnerClipRect.Max.y); } // " IM_ASSERT(0); @@ -10573,7 +10573,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++) { ImGuiTable* table = g.Tables.GetByIndex(table_n); - if (table->LastFrameActive < g.FrameCount - 1 || table->OuterWindow != g.NavWindow) + if (table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) continue; BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 93168cbd..15876be7 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3680,8 +3680,8 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("_NoSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_NoSortDescending); ImGui::CheckboxFlags("_PreferSortAscending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortAscending); ImGui::CheckboxFlags("_PreferSortDescending", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_PreferSortDescending); - ImGui::CheckboxFlags("_IndentEnable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentEnable); - ImGui::CheckboxFlags("_IndentDisable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentDisable); + ImGui::CheckboxFlags("_IndentEnable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentEnable); ImGui::SameLine(); HelpMarker("Default for column 0"); + ImGui::CheckboxFlags("_IndentDisable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentDisable); ImGui::SameLine(); HelpMarker("Default for column >0"); ImGui::PopID(); ImGui::PopStyleVar(2); } @@ -4089,7 +4089,6 @@ static void ShowDemoWindowTables() static int items_count = IM_ARRAYSIZE(template_items_names); static ImVec2 outer_size_value = ImVec2(0, 250); static float row_min_height = 0.0f; // Auto - static float inner_width_without_scroll = 0.0f; // Fill static float inner_width_with_scroll = 0.0f; // Auto-extend static bool outer_size_enabled = true; static bool lock_first_column_visibility = false; @@ -4171,10 +4170,7 @@ static void ShowDemoWindowTables() // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. // To facilitate experimentation we expose two values and will select the right one depending on active flags. - if (flags & ImGuiTableFlags_ScrollX) - ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); - else - ImGui::DragFloat("inner_width (when ScrollX inactive)", &inner_width_without_scroll, 1.0f, 0.0f, FLT_MAX); + ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000); @@ -4213,7 +4209,7 @@ static void ShowDemoWindowTables() ImVec2 table_scroll_cur, table_scroll_max; // For debug display const ImDrawList* table_draw_list = NULL; // " - const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : inner_width_without_scroll; + const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : 0.0f; if (ImGui::BeginTable("##table", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) { // Declare columns diff --git a/imgui_internal.h b/imgui_internal.h index 62385a77..c77d3081 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1905,11 +1905,11 @@ struct ImGuiTableColumn float ContentMaxPosRowsFrozen; // Submitted contents absolute maximum position, from which we can infer width. float ContentMaxPosRowsUnfrozen; // (kept as float because we need to manipulate those between each cell change) float ContentMaxPosHeadersUsed; - float ContentMaxPosHeadersDesired; + float ContentMaxPosHeadersIdeal; ImS16 ContentWidthRowsFrozen; // Contents width. Because row freezing is not correlated with headers/not-headers we need all 4 variants (ImDrawCmd merging uses different data than alignment code). ImS16 ContentWidthRowsUnfrozen; // (encoded as ImS16 because we actually rarely use those width) ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls - ImS16 ContentWidthHeadersDesired; + ImS16 ContentWidthHeadersIdeal; ImS16 NameOffset; // Offset into parent ColumnsNames[] bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. bool IsActiveNextFrame; @@ -1982,7 +1982,8 @@ struct ImGuiTable float LastOuterHeight; // Outer height from last frame float LastFirstRowHeight; // Height of first row from last frame float ColumnsTotalWidth; - float InnerWidth; + float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details. + float IdealTotalWidth; // Sum of ideal column width for nothing to be clipped float ResizedColumnNextWidth; ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect WorkRect; @@ -2264,14 +2265,14 @@ namespace ImGui IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); IMGUI_API void TableEndCell(ImGuiTable* table); IMGUI_API ImRect TableGetCellRect(); - IMGUI_API const char* TableGetColumnName(ImGuiTable* table, int column_n); - IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0); + IMGUI_API const char* TableGetColumnName(const ImGuiTable* table, int column_n); + IMGUI_API ImGuiID TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no = 0); IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); IMGUI_API void TableLoadSettings(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table); - IMGUI_API ImGuiTableSettings* TableFindSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableFindSettings(const ImGuiTable* table); IMGUI_API void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); IMGUI_API void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); IMGUI_API void TableSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 025f144e..ae209ec5 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -109,8 +109,8 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) if (flags & ImGuiTableFlags_Resizable) flags |= ImGuiTableFlags_BordersVInner; - // Adjust flags: disable top rows freezing if there's no scrolling - // In theory we could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. + // Adjust flags: disable top rows freezing if there's no scrolling. + // We could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. if ((flags & ImGuiTableFlags_ScrollX) == 0) flags &= ~ImGuiTableFlags_ScrollFreezeColumnsMask_; if ((flags & ImGuiTableFlags_ScrollY) == 0) @@ -120,7 +120,8 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) flags &= ~ImGuiTableFlags_NoHostExtendY; - // Adjust flags: we don't support NoClipX with (FreezeColumns > 0), we could with some work but it doesn't appear to be worth the effort + // Adjust flags: we don't support NoClipX with (FreezeColumns > 0) + // We could with some work but it doesn't appear to be worth the effort. if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) flags &= ~ImGuiTableFlags_NoClipX; @@ -133,28 +134,32 @@ ImGuiTable* ImGui::FindTableByID(ImGuiID id) return g.Tables.GetByKey(id); } -// About 'outer_size': -// The meaning of outer_size needs to differ slightly depending of if we are using ScrollX/ScrollY flags. -// With ScrollX/ScrollY: using a child window for scrolling: +// (Read carefully because this is subtle but it does make sense!) +// About 'outer_size', its meaning needs to differ slightly depending of if we are using ScrollX/ScrollY flags: +// X: +// - outer_size.x < 0.0f -> right align from window/work-rect maximum x edge. +// - outer_size.x = 0.0f -> auto enlarge, use all available space. +// - outer_size.x > 0.0f -> fixed width +// Y with ScrollX/ScrollY: using a child window for scrolling: // - outer_size.y < 0.0f -> bottom align -// - outer_size.y = 0.0f -> bottom align: consistent with BeginChild(), best to preserve (0,0) default arg -// - outer_size.y > 0.0f -> fixed child height -// Without scrolling, we output table directly in parent window: +// - outer_size.y = 0.0f -> bottom align, consistent with BeginChild(). not recommended unless table is last item in parent window. +// - outer_size.y > 0.0f -> fixed child height. recommended when using Scrolling on any axis. +// Y without scrolling, we output table directly in parent window: // - outer_size.y < 0.0f -> bottom align (will auto extend, unless NoHostExtendV is set) // - outer_size.y = 0.0f -> zero minimum height (will auto extend, unless NoHostExtendV is set) // - outer_size.y > 0.0f -> minimum height (will auto extend, unless NoHostExtendV is set) -// About: 'inner_width': +// About 'inner_width': // With ScrollX: // - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird -// - inner_width = 0.0f -> auto enlarge: *only* fixed size columns, which will take space they need (proportional columns becomes fixed columns) <-- desired default :( -// - inner_width > 0.0f -> fit in known width: fixed column take space they need if possible (otherwise shrink down), proportional columns share remaining space. +// - inner_width = 0.0f -> fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes Fixed columns. +// - inner_width > 0.0f -> override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise shrink down), Stretch columns share remaining space! // Without ScrollX: -// - inner_width < 0.0f -> fit in known width (right align from outer_size.x) <-- desired default -// - inner_width = 0.0f -> auto enlarge: will emit contents size in parent window -// - inner_width > 0.0f -> fit in known width (bypass outer_size.x, permitted but not useful, should instead alter outer_width) -// FIXME-TABLE: This is currently not very useful. -// FIXME-TABLE: Replace enlarge vs fixed width by a flag. -// Even if not really useful, we allow 'inner_scroll_width < outer_size.x' for consistency and to facilitate understanding of what the value does. +// - inner_width -> *ignored* +// Details: +// - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept +// of "available space" doesn't make sense. +// - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding +// of what the value does. bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) { ImGuiID id = GetID(str_id); @@ -213,15 +218,17 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (use_child_window) { - // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) + // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent + // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) ImVec2 override_content_size(FLT_MAX, FLT_MAX); if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) override_content_size.y = FLT_MIN; - // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and never lead to any scrolling) - // We don't handle inner_width < 0.0f, we could potentially use it to right-align based on the right side of the child window work rect, - // which would require knowing ahead if we are going to have decoration taking horizontal spaces (typically a vertical scrollbar). - if (inner_width != 0.0f) + // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and + // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align + // based on the right side of the child window work rect, which would require knowing ahead if we are going to + // have decoration taking horizontal spaces (typically a vertical scrollbar). + if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f) override_content_size.x = inner_width; if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) @@ -322,7 +329,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG TableLoadSettings(table); // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. - // This is not strictly necessary but will reduce cases were misleading "out of table" output will be confusing to the user. + // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. inner_window->SkipItems = true; @@ -373,7 +380,8 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImS8)reorder_dir; IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); - // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], rebuild the later from the former. + // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[], + // rebuild the later from the former. for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; table->ReorderColumnDir = 0; @@ -452,10 +460,13 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) void ImGui::TableUpdateDrawChannels(ImGuiTable* table) { // Allocate draw channels. - // - We allocate them following the storage order instead of the display order so reordering columns won't needlessly increase overall dormant memory cost. - // - We isolate headers draw commands in their own channels instead of just altering clip rects. This is in order to facilitate merging of draw commands. - // - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of draw channels. - // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. + // - We allocate them following storage order instead of display order so reordering columns won't needlessly + // increase overall dormant memory cost. + // - We isolate headers draw commands in their own channels instead of just altering clip rects. + // This is in order to facilitate merging of draw commands. + // - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels. + // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other + // channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. // Draw channel allocation (before merging): // - NoClip --> 1+1 channels: background + foreground (same clip rect == 1 draw call) // - Clip --> 1+N channels @@ -536,21 +547,24 @@ static float TableGetMinColumnWidth() // Layout columns for the frame // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first. -// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAlwaysAutoResize columns, -// increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution for WidthAlwaysAutoResize columns? +// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for WidthAlwaysAutoResize +// columns, increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution +// for WidthAlwaysAutoResize columns? void ImGui::TableUpdateLayout(ImGuiTable* table) { IM_ASSERT(table->IsLayoutLocked == false); // Compute offset, clip rect for the frame + // (can't make auto padding larger than what WorkRect knows about so right-alignment matches) const ImRect work_rect = table->WorkRect; - const float padding_auto_x = table->CellPaddingX2; // Can't make auto padding larger than what WorkRect knows about so right-alignment matches. + const float padding_auto_x = table->CellPaddingX2; const float min_column_width = TableGetMinColumnWidth(); int count_fixed = 0; float width_fixed = 0.0f; float total_weights = 0.0f; table->LeftMostStretchedColumnDisplayOrder = -1; + table->IdealTotalWidth = 0.0f; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) @@ -569,6 +583,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (table->Flags & ImGuiTableFlags_Sortable) TableFixColumnSortDirection(column); + // Calculate "ideal" column width for nothing to be clipped. + // Combine width from regular rows + width from headers unless requested not to. + const float column_content_width_rows = (float)ImMax(column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen); + const float column_content_width_headers = (float)column->ContentWidthHeadersIdeal; + float column_width_ideal = column_content_width_rows; + if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) + column_width_ideal = ImMax(column_width_ideal, column_content_width_headers); + column_width_ideal = ImMax(column_width_ideal + padding_auto_x, min_column_width); + table->IdealTotalWidth += column_width_ideal; + if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) { // Latch initial size for fixed columns @@ -576,14 +600,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const bool init_size = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); if (init_size) { - // Combine width from regular rows + width from headers unless requested not to - float width_request = (float)ImMax(column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen); - if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) - width_request = ImMax(width_request, (float)column->ContentWidthHeadersDesired); - column->WidthRequested = ImMax(width_request + padding_auto_x, min_column_width); + column->WidthRequested = column_width_ideal; - // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets (e.g. TextWrapped) too much. - // Otherwise what tends to happen is that TextWrapped would output a very large height (= first frame scrollbar display very off + clipper would skip lots of items) + // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets + // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very + // large height (= first frame scrollbar display very off + clipper would skip lots of items). // This is merely making the side-effect less extreme, but doesn't properly fixes it. if (column->AutoFitQueue > 0x01 && table->IsInitializing) column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); @@ -606,7 +627,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1); float width_avail; - if ((table->Flags & ImGuiTableFlags_ScrollX) && (table->InnerWidth == 0.0f)) + if ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; else width_avail = work_rect.GetWidth() - width_spacings - 1.0f; @@ -630,15 +651,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->WidthRequested = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); width_remaining_for_stretched_columns -= column->WidthRequested; - // [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing to left-side of fixed column - // We also need to copy the NoResize flag.. + // [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing + // to left-side of fixed column. We also need to copy the NoResize flag.. if (column->NextActiveColumn != -1) if (ImGuiTableColumn* next_column = &table->Columns[column->NextActiveColumn]) if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed) column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_); } - // [Resize Rule 1] The right-most active column is not resizable if there is at least one Stretch column (see comments in TableResizeColumn().) + // [Resize Rule 1] The right-most active column is not resizable if there is at least one Stretch column + // (see comments in TableResizeColumn().) if (column->NextActiveColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1) column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; @@ -675,8 +697,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else #endif - // Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column) - // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending where the mouse cursor is and/or relative weights. + // Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). + // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending where + // the mouse cursor is and/or relative weights. // FIXME-TABLE: May be simpler to store floating width and floor final positions only // FIXME-TABLE: Make it optional? User might prefer to preserve pixel perfect same size? if (width_remaining_for_stretched_columns >= 1.0f) @@ -707,7 +730,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) { // Hidden column: clear a few fields and we are done with it for the remainder of the function. - // We set a zero-width clip rect however we pay attention to set Min.y/Max.y properly to not interfere with the clipper. + // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. column->MinX = column->MaxX = offset_x; column->StartXRows = column->StartXHeaders = offset_x; column->WidthGiven = 0.0f; @@ -729,8 +752,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } else { - // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make sure they are all visible. - // Because of this we also know that all of the columns will always fit in table->WorkRect and therefore in table->InnerRect (because ScrollX is off) + // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make + // sure they are all visible. Because of this we also know that all of the columns will always fit in + // table->WorkRect and therefore in table->InnerRect (because ScrollX is off) if (!(table->Flags & ImGuiTableFlags_NoKeepColumnsVisible)) max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; } @@ -760,8 +784,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; // Alignment - // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in many cases. - // (To be able to honor this we might be able to store a log of cells width, per row, for visible rows, but nav/programmatic scroll would have visible artifacts.) + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in + // many cases (to be able to honor this we might be able to store a log of cells width, per row, for + // visible rows, but nav/programmatic scroll would have visible artifacts.) //if (column->Flags & ImGuiTableColumnFlags_AlignRight) // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen); //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) @@ -770,7 +795,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Reset content width variables const float initial_max_pos_x = column->MinX + table->CellPaddingX1; column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; - column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersDesired = initial_max_pos_x; + column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersIdeal = initial_max_pos_x; } // Don't decrement auto-fit counters until container window got a chance to submit its items @@ -787,7 +812,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) active_n++; } - // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either because of using _WidthAlwaysAutoResize/_WidthStretch) + // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, + // either because of using _WidthAlwaysAutoResize/_WidthStretch). // This will hide the resizing option from the context menu. if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable)) table->Flags &= ~ImGuiTableFlags_Resizable; @@ -828,15 +854,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Process interaction on resizing borders. Actual size change will be applied in EndTable() // - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise -// - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets overlapping the same area. +// - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets +// overlapping the same area. void ImGui::TableUpdateBorders(ImGuiTable* table) { ImGuiContext& g = *GImGui; IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); - // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and use - // the final height from last frame. Because this is only affecting _interaction_ with columns, it is not really problematic. - // (whereas the actual visual will be displayed in EndTable() and using the current frame height) + // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and + // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not + // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). // Actual columns highlight/render will be performed in EndTable() and not be affected. const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersVFullHeight); const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; @@ -895,8 +922,8 @@ void ImGui::EndTable() ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); - // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some cases, - // and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) + // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some + // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); // If the user never got to call TableNextRow() or TableNextCell(), we call layout ourselves to ensure all our @@ -942,7 +969,7 @@ void ImGui::EndTable() column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); - column->ContentWidthHeadersDesired = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersDesired - ref_x_headers); + column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) max_pos_x = ImMax(max_pos_x, column->MaxX); @@ -1084,9 +1111,9 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if (table->Flags & ImGuiTableFlags_BordersOuter) { // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call - // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their parent. - // In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part of it in inner window, - // and the part that's over scrollbars in the outer window..) + // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their + // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part + // of it in inner window, and the part that's over scrollbars in the outer window..) // Either solution currently won't allow us to use a larger border size: the border would clipped. ImRect outer_border = table->OuterRect; const ImU32 outer_col = table->BorderColorStrong; @@ -1193,8 +1220,8 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) { - // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to - // ensure that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others. + // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure + // that our left border won't move, which we can do by making sure column_a/column_b resizes cancels each others. if (column_1 && (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)) if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->DisplayOrder) { @@ -1240,12 +1267,15 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group // based on its position (within frozen rows/columns groups or not). -// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be merged by the DrawSplitter.Merge() call. +// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be +// merged by the DrawSplitter.Merge() call. // // Column channels will not be merged into one of the 1-4 groups in the following cases: // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). -// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds matches, by e.g. calling SetCursorScreenPos(). -// - The channel uses more than one draw command itself. We drop all our merging stuff here.. we could do better but it's going to be rare. +// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds +// matches, by e.g. calling SetCursorScreenPos(). +// - The channel uses more than one draw command itself. We drop all our merging stuff here.. we could do better +// but it's going to be rare. // // This function is particularly tricky to understand.. take a breath. void ImGui::TableDrawMergeChannels(ImGuiTable* table) @@ -1308,13 +1338,13 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); merge_group_mask |= (1 << merge_group_dst_n); - // If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command with - // the existing outer window command. But we can only do so if our columns all fit within the expected clip rect, - // otherwise clipping will be incorrect when ScrollX is disabled. + // If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command + // with the existing outer window command. But we can only do so if our columns all fit within the expected + // clip rect, otherwise clipping will be incorrect when ScrollX is disabled. // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column don't fit within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect - // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to fix... - // cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." + // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to + // fix... cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." // 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary... #if 0 if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) @@ -1322,7 +1352,8 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) #endif } - // Invalidate current draw channel (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging) + // Invalidate current draw channel + // (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging) column->DrawChannelCurrent = -1; } @@ -1402,8 +1433,9 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; table->DeclColumnsCount++; - // When passing a width automatically enforce WidthFixed policy (vs TableFixColumnFlags would default to WidthAlwaysAutoResize) - // (we write down to FlagsIn which is a little misleading, another solution would be to pass init_width_or_weight to TableFixColumnFlags) + // When passing a width automatically enforce WidthFixed policy + // (vs TableFixColumnFlags would default to WidthAlwaysAutoResize) + // (we write to FlagsIn which is a little misleading, another solution would be to pass init_width_or_weight to TableFixColumnFlags) if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) if ((table->Flags & ImGuiTableFlags_SizingPolicyFixedX) && (init_width_or_weight > 0.0f)) flags |= ImGuiTableColumnFlags_WidthFixed; @@ -1521,8 +1553,8 @@ void ImGui::TableEndRow(ImGuiTable* table) TableEndCell(table); - // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. - // However it is likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. + // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is + // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. window->DC.CursorPos.y = table->RowPosY2; // Row background fill @@ -1588,7 +1620,8 @@ void ImGui::TableEndRow(ImGuiTable* table) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong); // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) - // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and get the new cursor position. + // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and + // get the new cursor position. if (unfreeze_rows) { IM_ASSERT(table->IsFreezeRowsPassed == false); @@ -1767,16 +1800,16 @@ ImRect ImGui::TableGetCellRect() return ImRect(column->MinX, table->RowPosY1, column->MaxX, table->RowPosY2); } -const char* ImGui::TableGetColumnName(ImGuiTable* table, int column_n) +const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) { - ImGuiTableColumn* column = &table->Columns[column_n]; + const ImGuiTableColumn* column = &table->Columns[column_n]; if (column->NameOffset == -1) return NULL; return &table->ColumnsNames.Buf[column->NameOffset]; } // Return the resizing ID for the right-side of the given column. -ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no) +ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no) { IM_ASSERT(column_n < table->ColumnsCount); ImGuiID id = table->ID + (instance_no * table->ColumnsCount) + column_n; @@ -1880,8 +1913,8 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) } // This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). -// The intent is that advanced users willing to create customized headers would not need to use this helper and may create their own. -// However presently this function uses too many internal structures/calls. +// The intent is that advanced users willing to create customized headers would not need to use this helper and may +// create their own. However presently this function uses too many internal structures/calls. void ImGui::TableAutoHeaders() { ImGuiContext& g = *GImGui; @@ -1964,7 +1997,7 @@ void ImGui::TableAutoHeaders() window->DC.CursorPos.y -= g.Style.ItemSpacing.y; window->DC.CursorMaxPos = backup_cursor_max_pos; // Don't feed back into the width of the Header row - // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden. if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) open_context_popup = -1; } @@ -2024,7 +2057,8 @@ void ImGui::TableHeader(const char* label) // FIXME-TABLE: Fix when padding are disabled. //window->DC.CursorPos.x = column->MinX + table->CellPadding.x; - // Keep header highlighted when context menu is open. (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) + // Keep header highlighted when context menu is open. + // (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld | ImGuiSelectableFlags_DontClosePopups, ImVec2(0.0f, label_height)); const bool held = IsItemActive(); @@ -2091,16 +2125,17 @@ void ImGui::TableHeader(const char* label) TableSortSpecsClickColumn(table, column, g.IO.KeyShift); } - // Render clipped label - // Clipping here ensure that in the majority of situations, all our header cells will be merged into a single draw call. + // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will + // be merged into a single draw call. //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); - // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging. + // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering + // for merging. // FIXME-TABLE: Clarify policies of how label width and potential decorations (arrows) fit into auto-resize of the column float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; column->ContentMaxPosHeadersUsed = ImMax(column->ContentMaxPosHeadersUsed, work_r.Max.x);// ImMin(max_pos_x, work_r.Max.x)); - column->ContentMaxPosHeadersDesired = ImMax(column->ContentMaxPosHeadersDesired, max_pos_x); + column->ContentMaxPosHeadersIdeal = ImMax(column->ContentMaxPosHeadersIdeal, max_pos_x); PopID(); } @@ -2144,7 +2179,8 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click } // Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) -// You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. +// You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since +// last call, or the first time. // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { @@ -2292,7 +2328,7 @@ static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id) return NULL; } -ImGuiTableSettings* ImGui::TableFindSettings(ImGuiTable* table) +ImGuiTableSettings* ImGui::TableFindSettings(const ImGuiTable* table) { if (table->SettingsOffset == -1) return NULL; @@ -2438,7 +2474,8 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan if (settings->ID == 0) // Skip ditched settings continue; - // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped (e.g. Order was unchanged) + // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped + // (e.g. Order was unchanged) const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; @@ -2473,6 +2510,7 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan //------------------------------------------------------------------------- #ifndef IMGUI_DISABLE_METRICS_WINDOW + void ImGui::DebugNodeTable(ImGuiTable* table) { char buf[256]; @@ -2482,33 +2520,33 @@ void ImGui::DebugNodeTable(ImGuiTable* table) bool open = TreeNode(table, "%s", buf); if (IsItemHovered()) GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); - if (open) + if (!open) + return; + BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s, IdealWidth: %.1f", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "", table->IdealTotalWidth); + for (int n = 0; n < table->ColumnsCount; n++) { - for (int n = 0; n < table->ColumnsCount; n++) - { - ImGuiTableColumn* column = &table->Columns[n]; - const char* name = TableGetColumnName(table, n); - BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" - "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" - "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" - "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Desired %d/%d\n" - "SortOrder: %d, SortDir: %s\n" - "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", - n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, - column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, - column->WidthGiven, column->WidthRequested, column->ResizeWeight, - column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersDesired, - column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", - column->UserID, column->Flags, - (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", - (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", - (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "", - (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); - } - if (ImGuiTableSettings* settings = TableFindSettings(table)) - DebugNodeTableSettings(settings); - TreePop(); + ImGuiTableColumn* column = &table->Columns[n]; + const char* name = TableGetColumnName(table, n); + BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" + "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" + "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" + "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Ideal %d/%d\n" + "SortOrder: %d, SortDir: %s\n" + "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", + n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, + column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, + column->WidthGiven, column->WidthRequested, column->ResizeWeight, + column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersIdeal, + column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", + column->UserID, column->Flags, + (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", + (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", + (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "", + (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); } + if (ImGuiTableSettings* settings = TableFindSettings(table)) + DebugNodeTableSettings(settings); + TreePop(); } void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) From 9b6d0fdb7a04b80436b4749801a002f4c57f811e Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 13 May 2020 13:30:41 +0200 Subject: [PATCH 043/144] Tables: Fixed ignoring DefaultHide or DefaultSort data from flags when loading settings that don't have them. --- imgui_internal.h | 5 ++--- imgui_tables.cpp | 15 +++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index c77d3081..03ad431b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1952,7 +1952,7 @@ struct ImGuiTable ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map ImU64 VisibleMaskByIndex; // Visible (== Active and not Clipped) - ImGuiTableFlags SettingsSaveFlags; // Pre-compute which data we are going to save into the .ini file (e.g. when order is not altered we won't save order) + ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; int ColumnsCount; // Number of columns declared in BeginTable() @@ -2022,7 +2022,6 @@ struct ImGuiTable bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). bool IsSettingsRequestLoad; - bool IsSettingsLoaded; bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) bool IsResetDisplayOrderRequest; @@ -2050,7 +2049,7 @@ struct ImGuiTableColumnSettings ImS8 DisplayOrder; ImS8 SortOrder; ImS8 SortDirection : 7; - ImU8 Visible : 1; // This is called Active in ImGuiTableColumn, in .ini file we call it Visible. + ImU8 Visible : 1; // This is called Active in ImGuiTableColumn, but in user-facing code we call this Visible (thus in .ini file) ImGuiTableColumnSettings() { diff --git a/imgui_tables.cpp b/imgui_tables.cpp index ae209ec5..80c1f89b 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1466,12 +1466,12 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, column->ResizeWeight = 1.0f; } } - if (table->IsInitializing && !table->IsSettingsLoaded) + if (table->IsInitializing) { // Init default visibility/sort state - if (flags & ImGuiTableColumnFlags_DefaultHide) + if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) column->IsActive = column->IsActiveNextFrame = false; - if (flags & ImGuiTableColumnFlags_DefaultSort) + if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) { column->SortOrder = 0; // Multiple columns using _DefaultSort will be reordered when building the sort specs. column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); @@ -2360,7 +2360,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) } settings->ColumnsCount = (ImS8)table->ColumnsCount; - // Serialize ImGuiTableSettings/ImGuiTableColumnSettings --> ImGuiTable/ImGuiTableColumn + // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings IM_ASSERT(settings->ID == table->ID); IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount); ImGuiTableColumn* column = table->Columns.Data; @@ -2378,7 +2378,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->Visible = column->IsActive; // We skip saving some data in the .ini file when they are unnecessary to restore our state - // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet. + // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved. if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column_settings->SortOrder != -1) @@ -2411,10 +2411,9 @@ void ImGui::TableLoadSettings(ImGuiTable* table) { settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); } - table->IsSettingsLoaded = true; - settings->SaveFlags = table->Flags; + table->SettingsLoadedFlags = settings->SaveFlags; - // Serialize ImGuiTable/ImGuiTableColumn --> ImGuiTableSettings/ImGuiTableColumnSettings + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) { From 95c273618eb81a5e16a0b726d6b7846460b63ccd Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 13 May 2020 20:24:37 +0200 Subject: [PATCH 044/144] Tables: Allow hot-reload of settings (merge policy), tidying up settings code --- imgui.cpp | 10 +--- imgui_internal.h | 11 ++--- imgui_tables.cpp | 120 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 91 insertions(+), 50 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 55c67bbb..7ad7a547 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3970,15 +3970,7 @@ void ImGui::Initialize(ImGuiContext* context) #ifdef IMGUI_HAS_TABLE // Add .ini handle for ImGuiTable type - { - ImGuiSettingsHandler ini_handler; - ini_handler.TypeName = "Table"; - ini_handler.TypeHash = ImHashStr("Table"); - ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; - ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; - ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; - g.SettingsHandlers.push_back(ini_handler); - } + TableInstallSettingsHandler(context); #endif // #ifdef IMGUI_HAS_TABLE #ifdef IMGUI_HAS_DOCK diff --git a/imgui_internal.h b/imgui_internal.h index 03ad431b..bd29e343 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2066,9 +2066,10 @@ struct ImGuiTableColumnSettings struct ImGuiTableSettings { ImGuiID ID; // Set to 0 to invalidate/delete the setting - ImGuiTableFlags SaveFlags; + ImGuiTableFlags SaveFlags; // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..) ImS8 ColumnsCount; - ImS8 ColumnsCountMax; + ImS8 ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher + bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) ImGuiTableSettings() { memset(this, 0, sizeof(*this)); } ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } @@ -2271,10 +2272,8 @@ namespace ImGui IMGUI_API void PopTableBackground(); IMGUI_API void TableLoadSettings(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table); - IMGUI_API ImGuiTableSettings* TableFindSettings(const ImGuiTable* table); - IMGUI_API void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); - IMGUI_API void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); - IMGUI_API void TableSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); + IMGUI_API ImGuiTableSettings* TableGetBoundSettings(const ImGuiTable* table); + IMGUI_API void TableInstallSettingsHandler(ImGuiContext* context); // Tab Bars IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 80c1f89b..b585f114 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2305,19 +2305,28 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) // [Main] 4: TableSettingsHandler_WriteAll() When .ini file is dirty (which can come from other source), save TableSettings into .ini file. //------------------------------------------------------------------------- +// Clear and initialize empty settings instance +static void InitTableSettings(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max) +{ + IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); + ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings(); + for (int n = 0; n < columns_count_max; n++, settings_column++) + IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); + settings->ID = id; + settings->ColumnsCount = (ImS8)columns_count; + settings->ColumnsCountMax = (ImS8)columns_count_max; + settings->WantApply = true; +} + static ImGuiTableSettings* CreateTableSettings(ImGuiID id, int columns_count) { ImGuiContext& g = *GImGui; ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings)); - IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); - ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings(); - for (int n = 0; n < columns_count; n++, settings_column++) - IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); - settings->ID = id; - settings->ColumnsCount = settings->ColumnsCountMax = (ImS8)columns_count; + InitTableSettings(settings, id, columns_count, columns_count); return settings; } +// Find existing settings static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id) { // FIXME-OPT: Might want to store a lookup map for this? @@ -2328,20 +2337,19 @@ static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id) return NULL; } -ImGuiTableSettings* ImGui::TableFindSettings(const ImGuiTable* table) +// Get settings for a given table, NULL if none +ImGuiTableSettings* ImGui::TableGetBoundSettings(const ImGuiTable* table) { - if (table->SettingsOffset == -1) - return NULL; - - ImGuiContext& g = *GImGui; - ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); - IM_ASSERT(settings->ID == table->ID); - if (settings->ColumnsCountMax < table->ColumnsCount) + if (table->SettingsOffset != -1) { - settings->ID = 0; // Ditch storage if we won't fit because of a count change - return NULL; + ImGuiContext& g = *GImGui; + ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + IM_ASSERT(settings->ID == table->ID); + if (settings->ColumnsCountMax >= table->ColumnsCount) + return settings; // OK + settings->ID = 0; // Invalidate storage, we won't fit because of a count change } - return settings; + return NULL; } void ImGui::TableSaveSettings(ImGuiTable* table) @@ -2352,7 +2360,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) // Bind or create settings data ImGuiContext& g = *GImGui; - ImGuiTableSettings* settings = TableFindSettings(table); + ImGuiTableSettings* settings = TableGetBoundSettings(table); if (settings == NULL) { settings = CreateTableSettings(table->ID, table->ColumnsCount); @@ -2370,7 +2378,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) settings->SaveFlags = ImGuiTableFlags_Resizable; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { - //column_settings->WidthOrWeight = column->WidthRequested; // FIXME-WIP + //column_settings->WidthOrWeight = column->WidthRequested; // FIXME-TABLE: Missing column_settings->Index = (ImS8)n; column_settings->DisplayOrder = column->DisplayOrder; column_settings->SortOrder = column->SortOrder; @@ -2378,7 +2386,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->Visible = column->IsActive; // We skip saving some data in the .ini file when they are unnecessary to restore our state - // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved. + // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column_settings->SortOrder != -1) @@ -2409,9 +2417,10 @@ void ImGui::TableLoadSettings(ImGuiTable* table) } else { - settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + settings = TableGetBoundSettings(table); } table->SettingsLoadedFlags = settings->SaveFlags; + IM_ASSERT(settings->ColumnsCount == table->ColumnsCount); // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); @@ -2422,14 +2431,13 @@ void ImGui::TableLoadSettings(ImGuiTable* table) continue; ImGuiTableColumn* column = &table->Columns[column_n]; //column->WidthRequested = column_settings->WidthOrWeight; // FIXME-WIP - if (column_settings->DisplayOrder != -1) + if (settings->SaveFlags & ImGuiTableFlags_Reorderable) column->DisplayOrder = column_settings->DisplayOrder; - if (column_settings->SortOrder != -1) - { - column->SortOrder = column_settings->SortOrder; - column->SortDirection = column_settings->SortDirection; - } + else + column->DisplayOrder = (ImS8)column_n; column->IsActive = column->IsActiveNextFrame = column_settings->Visible; + column->SortOrder = column_settings->SortOrder; + column->SortDirection = column_settings->SortDirection; } // FIXME-TABLE: Need to validate .ini data @@ -2437,16 +2445,46 @@ void ImGui::TableLoadSettings(ImGuiTable* table) table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImS8)column_n; } -void* ImGui::TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetSize(); i++) + g.Tables.GetByIndex(i)->SettingsOffset = -1; + g.SettingsTables.clear(); +} + +// Apply to existing windows (if any) +static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) +{ + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Tables.GetSize(); i++) + { + ImGuiTable* table = g.Tables.GetByIndex(i); + table->IsSettingsRequestLoad = true; + table->SettingsOffset = -1; + } +} + +static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { ImGuiID id = 0; int columns_count = 0; if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) return NULL; + + if (ImGuiTableSettings* settings = FindTableSettingsByID(id)) + { + if (settings->ColumnsCountMax >= columns_count) + { + InitTableSettings(settings, id, columns_count, settings->ColumnsCountMax); // Recycle + return settings; + } + settings->ID = 0; // Invalidate storage if we won't fit because of a count change + } return CreateTableSettings(id, columns_count); } -void ImGui::TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) +static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) { // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; @@ -2465,7 +2503,7 @@ void ImGui::TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } } -void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) +static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { ImGuiContext& g = *ctx; for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) @@ -2488,10 +2526,8 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) { // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" - if (column->UserID != 0) - buf->appendf("Column %-2d UserID=%08X", column_n, column->UserID); - else - buf->appendf("Column %-2d", column_n); + buf->appendf("Column %-2d", column_n); + if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); if (save_size) buf->appendf(" Width=%d", 0);// (int)settings_column->WidthOrWeight); // FIXME-TABLE if (save_visible) buf->appendf(" Visible=%d", column->Visible); if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); @@ -2502,6 +2538,20 @@ void ImGui::TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHan } } +void ImGui::TableInstallSettingsHandler(ImGuiContext* context) +{ + ImGuiContext& g = *context; + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Table"; + ini_handler.TypeHash = ImHashStr("Table"); + ini_handler.ClearAllFn = TableSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; + g.SettingsHandlers.push_back(ini_handler); +} + //------------------------------------------------------------------------- // TABLE - Debugging //------------------------------------------------------------------------- @@ -2543,7 +2593,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) ? "WidthAlwaysAutoResize " : "", (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); } - if (ImGuiTableSettings* settings = TableFindSettings(table)) + if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) DebugNodeTableSettings(settings); TreePop(); } From 466b6e619a1a4fe764e84f2c9131a13eaa2e3f0a Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 13 May 2020 22:24:20 +0200 Subject: [PATCH 045/144] Tables: Fixed incorrect application of CursorMaxPos.x (3162) --- imgui_tables.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b585f114..b6a526e8 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -971,12 +971,12 @@ void ImGui::EndTable() column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); + // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) - max_pos_x = ImMax(max_pos_x, column->MaxX); + max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); } - // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. - inner_window->DC.CursorMaxPos.x = max_pos_x + 1; + inner_window->DC.CursorMaxPos.x = max_pos_x; if (!(flags & ImGuiTableFlags_NoClipX)) inner_window->DrawList->PopClipRect(); From dff26191bd8dd7b6f8061b025322bc0e5e76d769 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 13 May 2020 23:42:22 +0200 Subject: [PATCH 046/144] Tables: Try to report contents width to outer window, generally better auto-fit. --- imgui_demo.cpp | 18 ++++++++++++ imgui_internal.h | 4 +-- imgui_tables.cpp | 71 +++++++++++++++++++++++++++++------------------- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 15876be7..b91ec0ab 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3570,6 +3570,24 @@ static void ShowDemoWindowTables() } ImGui::EndTable(); } + + if (ImGui::BeginTable("##table2", 3, flags | ImGuiTableFlags_SizingPolicyFixedX)) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableAutoHeaders(); + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Fixed %d,%d", row, column); + } + } + ImGui::EndTable(); + } ImGui::TreePop(); } diff --git a/imgui_internal.h b/imgui_internal.h index bd29e343..1d5b7020 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1981,9 +1981,9 @@ struct ImGuiTable float CellSpacingX; // Spacing between non-bordered cells float LastOuterHeight; // Outer height from last frame float LastFirstRowHeight; // Height of first row from last frame - float ColumnsTotalWidth; float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details. - float IdealTotalWidth; // Sum of ideal column width for nothing to be clipped + float ColumnsTotalWidth; // Sum of current column width + float ColumnsAutoFitWidth; // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window float ResizedColumnNextWidth; ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect WorkRect; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b6a526e8..29b1c9b1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -558,13 +558,14 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // (can't make auto padding larger than what WorkRect knows about so right-alignment matches) const ImRect work_rect = table->WorkRect; const float padding_auto_x = table->CellPaddingX2; + const float spacing_auto_x = table->CellSpacingX * (1.0f + 2.0f); // CellSpacingX is >0.0f when there's no vertical border, in which case we add two extra CellSpacingX to make auto-fit look nice instead of cramped. We may want to expose this somehow. const float min_column_width = TableGetMinColumnWidth(); int count_fixed = 0; float width_fixed = 0.0f; float total_weights = 0.0f; table->LeftMostStretchedColumnDisplayOrder = -1; - table->IdealTotalWidth = 0.0f; + table->ColumnsAutoFitWidth = 0.0f; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) @@ -591,7 +592,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) column_width_ideal = ImMax(column_width_ideal, column_content_width_headers); column_width_ideal = ImMax(column_width_ideal + padding_auto_x, min_column_width); - table->IdealTotalWidth += column_width_ideal; + table->ColumnsAutoFitWidth += column_width_ideal; if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) { @@ -623,6 +624,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } } + // CellSpacingX is >0.0f when there's no vertical border, in which case we add two extra CellSpacingX to make auto-fit look nice instead of cramped. + // We may want to expose this somehow. + table->ColumnsAutoFitWidth += spacing_auto_x * (table->ColumnsActiveCount - 1); + // Layout // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1); @@ -956,28 +961,6 @@ void ImGui::EndTable() table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); table->LastOuterHeight = table->OuterRect.GetHeight(); - // Store content width reference for each column - float max_pos_x = inner_window->DC.CursorMaxPos.x; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - { - ImGuiTableColumn* column = &table->Columns[column_n]; - - // Store content width (for both Headers and Rows) - //float ref_x = column->MinX; - float ref_x_rows = column->StartXRows - table->CellPaddingX1; - float ref_x_headers = column->StartXHeaders - table->CellPaddingX1; - column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); - column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); - column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); - column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); - - // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. - if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) - max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); - } - - inner_window->DC.CursorMaxPos.x = max_pos_x; - if (!(flags & ImGuiTableFlags_NoClipX)) inner_window->DrawList->PopClipRect(); inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); @@ -1015,16 +998,16 @@ void ImGui::EndTable() } // Layout in outer window + const float backup_outer_cursor_pos_x = outer_window->DC.CursorPos.x; + const float backup_outer_max_pos_x = outer_window->DC.CursorMaxPos.x; + const float backup_inner_max_pos_x = inner_window->DC.CursorMaxPos.x; inner_window->WorkRect = table->HostWorkRect; inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; outer_window->DC.ColumnsOffset.x = 0.0f; if (inner_window != outer_window) { - // Override EndChild's ItemSize with our own to enable auto-resize on the X axis when possible - float backup_outer_cursor_pos_x = outer_window->DC.CursorPos.x; EndChild(); - outer_window->DC.CursorMaxPos.x = backup_outer_cursor_pos_x + table->ColumnsTotalWidth + 1.0f + inner_window->ScrollbarSizes.x; } else { @@ -1034,6 +1017,38 @@ void ImGui::EndTable() ItemSize(item_size); } + // Store content width reference for each column + float max_pos_x = backup_inner_max_pos_x; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Store content width (for both Headers and Rows) + //float ref_x = column->MinX; + float ref_x_rows = column->StartXRows - table->CellPaddingX1; + float ref_x_headers = column->StartXHeaders - table->CellPaddingX1; + column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); + column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); + column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); + column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); + + // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. + if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) + max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); + } + + // Override EndChild/ItemSize max extent with our own to enable auto-resize on the X axis when possible + // FIXME-TABLE: This can be improved (e.g. for Fixed columns we don't want to auto AutoFitWidth? or propagate window auto-fit to table?) + if (table->Flags & ImGuiTableFlags_ScrollX) + { + inner_window->DC.CursorMaxPos.x = max_pos_x; // Set contents width for scrolling + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos_x, backup_outer_cursor_pos_x + table->ColumnsTotalWidth + 1.0f + inner_window->ScrollbarSizes.x); // For auto-fit + } + else + { + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos_x, table->WorkRect.Min.x + table->ColumnsAutoFitWidth); // For auto-fit + } + // Save settings if (table->IsSettingsDirty) TableSaveSettings(table); @@ -2571,7 +2586,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); if (!open) return; - BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s, IdealWidth: %.1f", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "", table->IdealTotalWidth); + BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s, ColumnsWidth: %.1f, AutoFitWidth: %.1f", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "", table->ColumnsTotalWidth, table->ColumnsAutoFitWidth); for (int n = 0; n < table->ColumnsCount; n++) { ImGuiTableColumn* column = &table->Columns[n]; From 23c60b2814da9a4fb2b4b7ff1bcbcd5704256c67 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 14 May 2020 17:57:25 +0200 Subject: [PATCH 047/144] Tables: Renamed internal fields: Active->Visible, Visible->VisibleUnclipped to be less misleading. --- imgui.h | 2 +- imgui_internal.h | 32 ++++----- imgui_tables.cpp | 165 ++++++++++++++++++++++++----------------------- 3 files changed, 100 insertions(+), 99 deletions(-) diff --git a/imgui.h b/imgui.h index f559da62..122facf4 100644 --- a/imgui.h +++ b/imgui.h @@ -687,7 +687,7 @@ namespace ImGui // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu // - Use TableAutoHeaders() to submit the whole header row, otherwise you may treat the header row as a regular row, manually call TableHeader() and other widgets. - // - Headers are required to perform some interactions: reordering, sorting, context menu // FIXME-TABLE: remove context from this list! + // - Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableHeader(const char* label); // submit one header cell manually. diff --git a/imgui_internal.h b/imgui_internal.h index 1d5b7020..d8d05245 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1888,7 +1888,7 @@ struct ImGuiTabBar #define IMGUI_TABLE_MAX_DRAW_CHANNELS (2 + 64 * 2) // See TableUpdateDrawChannels() // [Internal] sizeof() ~ 100 -// We use the terminology "Active" to refer to a column that is not Hidden by user or programmatically. We don't use the term "Visible" because it is ambiguous since an Active column can be non-visible because of scrolling. +// We use the terminology "Visible" to refer to a column that is not Hidden by user or settings. However it may still be out of view and clipped (see IsClipped). struct ImGuiTableColumn { ImRect ClipRect; // Clipping rectangle for the column @@ -1911,17 +1911,17 @@ struct ImGuiTableColumn ImS16 ContentWidthHeadersUsed; // TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls ImS16 ContentWidthHeadersIdeal; ImS16 NameOffset; // Offset into parent ColumnsNames[] - bool IsActive; // Is the column not marked Hidden by the user (regardless of clipping). We're not calling this "Visible" here because visibility also depends on clipping. - bool IsActiveNextFrame; - bool IsClipped; // Set when not overlapping the host window clipping rectangle. We don't use the opposite "!Visible" name because Clipped can be altered by events. + bool IsVisible; // Is the column not marked Hidden by the user? (could be clipped by scrolling, etc). + bool IsVisibleNextFrame; + bool IsClipped; // Set when not overlapping the host window clipping rectangle. bool SkipItems; ImS8 DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) - ImS8 IndexWithinActiveSet; // Index within active/visible set (<= IndexToDisplayOrder) + ImS8 IndexWithinVisibleSet; // Index within visible set (<= IndexToDisplayOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] ImS8 DrawChannelRowsBeforeFreeze; ImS8 DrawChannelRowsAfterFreeze; - ImS8 PrevActiveColumn; // Index of prev active column within Columns[], -1 if first active column - ImS8 NextActiveColumn; // Index of next active column within Columns[], -1 if last active column + ImS8 PrevVisibleColumn; // Index of prev visible column within Columns[], -1 if first visible column + ImS8 NextVisibleColumn; // Index of next visible column within Columns[], -1 if last visible column ImS8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit ImS8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem ImS8 SortOrder; // -1: Not sorting on this column @@ -1932,10 +1932,10 @@ struct ImGuiTableColumn memset(this, 0, sizeof(*this)); ResizeWeight = WidthRequested = WidthGiven = -1.0f; NameOffset = -1; - IsActive = IsActiveNextFrame = true; - DisplayOrder = IndexWithinActiveSet = -1; + IsVisible = IsVisibleNextFrame = true; + DisplayOrder = IndexWithinVisibleSet = -1; DrawChannelCurrent = DrawChannelRowsBeforeFreeze = DrawChannelRowsAfterFreeze = -1; - PrevActiveColumn = NextActiveColumn = -1; + PrevVisibleColumn = NextVisibleColumn = -1; AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames SortOrder = -1; SortDirection = ImGuiSortDirection_Ascending; @@ -1949,14 +1949,14 @@ struct ImGuiTable ImVector RawData; ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) - ImU64 ActiveMaskByIndex; // Column Index -> IsActive map (Active == not hidden by user/api) in a format adequate for iterating column without touching cold data - ImU64 ActiveMaskByDisplayOrder; // Column DisplayOrder -> IsActive map - ImU64 VisibleMaskByIndex; // Visible (== Active and not Clipped) + ImU64 VisibleMaskByIndex; // Column Index -> IsVisible map (== not hidden by user/api) in a format adequate for iterating column without touching cold data + ImU64 VisibleMaskByDisplayOrder; // Column DisplayOrder -> IsVisible map + ImU64 VisibleUnclippedMaskByIndex;// Visible and not Clipped, aka "actually visible" "not hidden by some scrolling" ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) int SettingsOffset; // Offset in g.SettingsTables int LastFrameActive; int ColumnsCount; // Number of columns declared in BeginTable() - int ColumnsActiveCount; // Number of non-hidden columns (<= ColumnsCount) + int ColumnsVisibleCount; // Number of non-hidden columns (<= ColumnsCount) int CurrentColumn; int CurrentRow; ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched. @@ -2007,7 +2007,7 @@ struct ImGuiTable ImS8 HeldHeaderColumn; // Index of column header being held. ImS8 ReorderColumn; // Index of column being reordered. (not cleared) ImS8 ReorderColumnDir; // -1 or +1 - ImS8 RightMostActiveColumn; // Index of right-most non-hidden column. + ImS8 RightMostVisibleColumn; // Index of right-most non-hidden column. ImS8 LeftMostStretchedColumnDisplayOrder; // Display order of left-most stretched column. ImS8 ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot ImS8 DummyDrawChannel; // Redirect non-visible columns here. @@ -2049,7 +2049,7 @@ struct ImGuiTableColumnSettings ImS8 DisplayOrder; ImS8 SortOrder; ImS8 SortDirection : 7; - ImU8 Visible : 1; // This is called Active in ImGuiTableColumn, but in user-facing code we call this Visible (thus in .ini file) + ImU8 Visible : 1; ImGuiTableColumnSettings() { diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 29b1c9b1..cec4ebb0 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -286,7 +286,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->DeclColumnsCount = 0; table->HoveredColumnBody = -1; table->HoveredColumnBorder = -1; - table->RightMostActiveColumn = -1; + table->RightMostVisibleColumn = -1; // Using opaque colors facilitate overlapping elements of the grid table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); @@ -333,7 +333,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. inner_window->SkipItems = true; - // Update/lock which columns will be Active for the frame + // Update/lock which columns will be Visible for the frame TableBeginUpdateColumns(table); return true; @@ -371,7 +371,7 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn]; - ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevActiveColumn : src_column->NextActiveColumn]; + ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevVisibleColumn : src_column->NextVisibleColumn]; IM_UNUSED(dst_column); const int src_order = src_column->DisplayOrder; const int dst_order = dst_column->DisplayOrder; @@ -398,10 +398,10 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->IsSettingsDirty = true; } - // Setup and lock Active state and order - table->ColumnsActiveCount = 0; + // Setup and lock Visible state and order + table->ColumnsVisibleCount = 0; table->IsDefaultDisplayOrder = true; - ImGuiTableColumn* last_active_column = NULL; + ImGuiTableColumn* last_visible_column = NULL; bool want_column_auto_fit = false; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { @@ -411,12 +411,12 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; column->NameOffset = -1; if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) - column->IsActiveNextFrame = true; - if (column->IsActive != column->IsActiveNextFrame) + column->IsVisibleNextFrame = true; + if (column->IsVisible != column->IsVisibleNextFrame) { - column->IsActive = column->IsActiveNextFrame; + column->IsVisible = column->IsVisibleNextFrame; table->IsSettingsDirty = true; - if (!column->IsActive && column->SortOrder != -1) + if (!column->IsVisible && column->SortOrder != -1) table->IsSortSpecsDirty = true; } if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_MultiSortable)) @@ -426,30 +426,30 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) ImU64 index_mask = (ImU64)1 << column_n; ImU64 display_order_mask = (ImU64)1 << column->DisplayOrder; - if (column->IsActive) + if (column->IsVisible) { - column->PrevActiveColumn = column->NextActiveColumn = -1; - if (last_active_column) + column->PrevVisibleColumn = column->NextVisibleColumn = -1; + if (last_visible_column) { - last_active_column->NextActiveColumn = (ImS8)column_n; - column->PrevActiveColumn = (ImS8)table->Columns.index_from_ptr(last_active_column); + last_visible_column->NextVisibleColumn = (ImS8)column_n; + column->PrevVisibleColumn = (ImS8)table->Columns.index_from_ptr(last_visible_column); } - column->IndexWithinActiveSet = (ImS8)table->ColumnsActiveCount; - table->ColumnsActiveCount++; - table->ActiveMaskByIndex |= index_mask; - table->ActiveMaskByDisplayOrder |= display_order_mask; - last_active_column = column; + column->IndexWithinVisibleSet = (ImS8)table->ColumnsVisibleCount; + table->ColumnsVisibleCount++; + table->VisibleMaskByIndex |= index_mask; + table->VisibleMaskByDisplayOrder |= display_order_mask; + last_visible_column = column; } else { - column->IndexWithinActiveSet = -1; - table->ActiveMaskByIndex &= ~index_mask; - table->ActiveMaskByDisplayOrder &= ~display_order_mask; + column->IndexWithinVisibleSet = -1; + table->VisibleMaskByIndex &= ~index_mask; + table->VisibleMaskByDisplayOrder &= ~display_order_mask; } - IM_ASSERT(column->IndexWithinActiveSet <= column->DisplayOrder); + IM_ASSERT(column->IndexWithinVisibleSet <= column->DisplayOrder); } - table->VisibleMaskByIndex = table->ActiveMaskByIndex; // Columns will be masked out by TableUpdateLayout() when Clipped - table->RightMostActiveColumn = (ImS8)(last_active_column ? table->Columns.index_from_ptr(last_active_column) : -1); + table->VisibleUnclippedMaskByIndex = table->VisibleMaskByIndex; // Columns will be masked out by TableUpdateLayout() when Clipped + table->RightMostVisibleColumn = (ImS8)(last_visible_column ? table->Columns.index_from_ptr(last_visible_column) : -1); // Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid // the column fitting to wait until the first visible frame of the child container (may or not be a good thing). @@ -473,9 +473,9 @@ void ImGui::TableUpdateDrawChannels(ImGuiTable* table) // - FreezeRows || FreezeColumns --> 1+N*2 (unless scrolling value is zero) // - FreezeRows && FreezeColunns --> 2+N*2 (unless scrolling value is zero) const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; - const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClipX) ? 1 : table->ColumnsActiveCount; + const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClipX) ? 1 : table->ColumnsVisibleCount; const int channels_for_background = 1; - const int channels_for_dummy = (table->ColumnsActiveCount < table->ColumnsCount || table->VisibleMaskByIndex != table->ActiveMaskByIndex) ? +1 : 0; + const int channels_for_dummy = (table->ColumnsVisibleCount < table->ColumnsCount || table->VisibleUnclippedMaskByIndex != table->VisibleMaskByIndex) ? +1 : 0; const int channels_total = channels_for_background + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total); table->DummyDrawChannel = channels_for_dummy ? (ImS8)(channels_total - 1) : -1; @@ -568,7 +568,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->ColumnsAutoFitWidth = 0.0f; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -626,11 +626,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // CellSpacingX is >0.0f when there's no vertical border, in which case we add two extra CellSpacingX to make auto-fit look nice instead of cramped. // We may want to expose this somehow. - table->ColumnsAutoFitWidth += spacing_auto_x * (table->ColumnsActiveCount - 1); + table->ColumnsAutoFitWidth += spacing_auto_x * (table->ColumnsVisibleCount - 1); // Layout // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible - const float width_spacings = table->CellSpacingX * (table->ColumnsActiveCount - 1); + const float width_spacings = table->CellSpacingX * (table->ColumnsVisibleCount - 1); float width_avail; if ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; @@ -645,7 +645,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->ColumnsTotalWidth = width_spacings; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; @@ -658,15 +658,15 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing // to left-side of fixed column. We also need to copy the NoResize flag.. - if (column->NextActiveColumn != -1) - if (ImGuiTableColumn* next_column = &table->Columns[column->NextActiveColumn]) + if (column->NextVisibleColumn != -1) + if (ImGuiTableColumn* next_column = &table->Columns[column->NextVisibleColumn]) if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed) column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_); } - // [Resize Rule 1] The right-most active column is not resizable if there is at least one Stretch column - // (see comments in TableResizeColumn().) - if (column->NextActiveColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1) + // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column + // (see comments in TableResizeColumn()) + if (column->NextVisibleColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1) column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; if (!(column->Flags & ImGuiTableColumnFlags_NoResize)) @@ -684,15 +684,15 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Shrink widths when the total does not fit // FIXME-TABLE: This is working but confuses/conflicts with manual resizing. // FIXME-TABLE: Policy to shrink down below below ideal/requested width if there's no room? - g.ShrinkWidthBuffer.resize(table->ColumnsActiveCount); - for (int order_n = 0, active_n = 0; order_n < table->ColumnsCount; order_n++) + g.ShrinkWidthBuffer.resize(table->ColumnsVisibleCount); + for (int order_n = 0, visible_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; const int column_n = table->DisplayOrder[order_n]; - g.ShrinkWidthBuffer[active_n].Index = column_n; - g.ShrinkWidthBuffer[active_n].Width = table->Columns[column_n].WidthGiven; - active_n++; + g.ShrinkWidthBuffer[visible_n].Index = column_n; + g.ShrinkWidthBuffer[visible_n].Width = table->Columns[column_n].WidthGiven; + visible_n++; } ShrinkWidths(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Size, width_excess); for (int n = 0; n < g.ShrinkWidthBuffer.Size; n++) @@ -710,7 +710,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (width_remaining_for_stretched_columns >= 1.0f) for (int order_n = table->ColumnsCount - 1; total_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) @@ -721,7 +721,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Setup final position, offset and clipping rectangles - int active_n = 0; + int visible_n = 0; float offset_x = (table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x; ImRect host_clip_rect = table->InnerClipRect; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) @@ -729,10 +729,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; - if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == active_n) + if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == visible_n) offset_x += work_rect.Min.x - table->OuterRect.Min.x; - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) { // Hidden column: clear a few fields and we are done with it for the remainder of the function. // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. @@ -761,7 +761,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // sure they are all visible. Because of this we also know that all of the columns will always fit in // table->WorkRect and therefore in table->InnerRect (because ScrollX is off) if (!(table->Flags & ImGuiTableFlags_NoKeepColumnsVisible)) - max_x = table->WorkRect.Max.x - (table->ColumnsActiveCount - (column->IndexWithinActiveSet + 1)) * min_column_width; + max_x = table->WorkRect.Max.x - (table->ColumnsVisibleCount - (column->IndexWithinVisibleSet + 1)) * min_column_width; } if (offset_x + column->WidthGiven > max_x) column->WidthGiven = ImMax(max_x - offset_x, min_column_width); @@ -781,7 +781,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (column->IsClipped) { // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. - table->VisibleMaskByIndex &= ~((ImU64)1 << column_n); + table->VisibleUnclippedMaskByIndex &= ~((ImU64)1 << column_n); } else { @@ -810,11 +810,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->CannotSkipItemsQueue >>= 1; } - if (active_n < table->FreezeColumnsCount) + if (visible_n < table->FreezeColumnsCount) host_clip_rect.Min.x = ImMax(host_clip_rect.Min.x, column->MaxX + 2.0f); offset_x += column->WidthGiven + table->CellSpacingX; - active_n++; + visible_n++; } // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, @@ -879,7 +879,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; const int column_n = table->DisplayOrderToIndex[order_n]; @@ -1033,7 +1033,7 @@ void ImGui::EndTable() column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. - if (table->ActiveMaskByIndex & ((ImU64)1 << column_n)) + if (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); } @@ -1094,7 +1094,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; const int column_n = table->DisplayOrderToIndex[order_n]; @@ -1103,7 +1103,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); - if (column->NextActiveColumn == -1 && !is_resizable) + if (column->NextVisibleColumn == -1 && !is_resizable) draw_right_border = false; if (draw_right_border && column->MaxX > column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. { @@ -1166,7 +1166,7 @@ static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (!column->IsActive || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; visible_weight += column->ResizeWeight; visible_width += column->WidthRequested; @@ -1177,7 +1177,7 @@ static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (!column->IsActive || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; column->ResizeWeight = (column->WidthRequested + 0.0f) / visible_width; } @@ -1201,14 +1201,14 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f float min_width = TableGetMinColumnWidth(); float max_width_0 = FLT_MAX; if (!(table->Flags & ImGuiTableFlags_ScrollX)) - max_width_0 = (table->WorkRect.Max.x - column_0->MinX) - (table->ColumnsActiveCount - (column_0->IndexWithinActiveSet + 1)) * min_width; + max_width_0 = (table->WorkRect.Max.x - column_0->MinX) - (table->ColumnsVisibleCount - (column_0->IndexWithinVisibleSet + 1)) * min_width; column_0_width = ImClamp(column_0_width, min_width, max_width_0); // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) if (column_0->WidthGiven == column_0_width || column_0->WidthRequested == column_0_width) return; - ImGuiTableColumn* column_1 = (column_0->NextActiveColumn != -1) ? &table->Columns[column_0->NextActiveColumn] : NULL; + ImGuiTableColumn* column_1 = (column_0->NextVisibleColumn != -1) ? &table->Columns[column_0->NextVisibleColumn] : NULL; // In this surprisingly not simple because of how we support mixing Fixed and Stretch columns. // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1. @@ -1315,7 +1315,7 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) // 1. Scan channels and take note of those which can be merged for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { - if (!(table->ActiveMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -1485,7 +1485,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, { // Init default visibility/sort state if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) - column->IsActive = column->IsActiveNextFrame = false; + column->IsVisible = column->IsVisibleNextFrame = false; if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) { column->SortOrder = 0; // Multiple columns using _DefaultSort will be reordered when building the sort specs. @@ -1692,7 +1692,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) window->WorkRect.Max.x = column->MaxX - table->CellPaddingX2; // To allow ImGuiListClipper to function we propagate our row height - if (!column->IsActive) + if (!column->IsVisible) window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); window->SkipItems = column->SkipItems; @@ -1752,7 +1752,7 @@ bool ImGui::TableNextCell() } int column_n = table->CurrentColumn; - return (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) != 0; + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } const char* ImGui::TableGetColumnName(int column_n) @@ -1766,6 +1766,7 @@ const char* ImGui::TableGetColumnName(int column_n) return TableGetColumnName(table, column_n); } +// We expose "Visible and Unclipped" to the user, vs our internal "Visible" state which is !Hidden bool ImGui::TableGetColumnIsVisible(int column_n) { ImGuiContext& g = *GImGui; @@ -1774,7 +1775,7 @@ bool ImGui::TableGetColumnIsVisible(int column_n) return false; if (column_n < 0) column_n = table->CurrentColumn; - return (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) != 0; + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } int ImGui::TableGetColumnIndex() @@ -1801,7 +1802,7 @@ bool ImGui::TableSetColumnIndex(int column_idx) TableBeginCell(table, column_idx); } - return (table->VisibleMaskByIndex & ((ImU64)1 << column_idx)) != 0; + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_idx)) != 0; } // Return the cell rectangle based on currently known height. @@ -1876,7 +1877,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) { if (ImGuiTableColumn* selected_column = (selected_column_n != -1) ? &table->Columns[selected_column_n] : NULL) { - const bool can_resize = !(selected_column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && selected_column->IsActive; + const bool can_resize = !(selected_column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && selected_column->IsVisible; if (MenuItem("Size column to fit", NULL, false, can_resize)) TableSetColumnAutofit(table, selected_column_n); } @@ -1886,7 +1887,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->IsActive) + if (column->IsVisible) TableSetColumnAutofit(table, column_n); } } @@ -1918,10 +1919,10 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) // Make sure we can't hide the last active column bool menu_item_active = (column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; - if (column->IsActive && table->ColumnsActiveCount <= 1) + if (column->IsVisible && table->ColumnsVisibleCount <= 1) menu_item_active = false; - if (MenuItem(name, NULL, column->IsActive, menu_item_active)) - column->IsActiveNextFrame = !column->IsActive; + if (MenuItem(name, NULL, column->IsVisible, menu_item_active)) + column->IsVisibleNextFrame = !column->IsVisible; } PopItemFlag(); } @@ -1995,8 +1996,8 @@ void ImGui::TableAutoHeaders() // FIXME-TABLE: This is not user-land code any more... perhaps instead we should expose hovered column. // and allow some sort of row-centric IsItemHovered() for full flexibility? float unused_x1 = table->WorkRect.Min.x; - if (table->RightMostActiveColumn != -1) - unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostActiveColumn].MaxX); + if (table->RightMostVisibleColumn != -1) + unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostVisibleColumn].MaxX); if (unused_x1 < table->WorkRect.Max.x) { // FIXME: We inherit ClipRect/SkipItem from last submitted column (active or not), let's temporarily override it. @@ -2091,14 +2092,14 @@ void ImGui::TableHeader(const char* label) // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) - if (ImGuiTableColumn* prev_column = (column->PrevActiveColumn != -1) ? &table->Columns[column->PrevActiveColumn] : NULL) + if (ImGuiTableColumn* prev_column = (column->PrevVisibleColumn != -1) ? &table->Columns[column->PrevVisibleColumn] : NULL) if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) - if ((column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinActiveSet < table->FreezeColumnsRequest)) + if ((column->IndexWithinVisibleSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinVisibleSet < table->FreezeColumnsRequest)) table->ReorderColumnDir = -1; if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) - if (ImGuiTableColumn* next_column = (column->NextActiveColumn != -1) ? &table->Columns[column->NextActiveColumn] : NULL) + if (ImGuiTableColumn* next_column = (column->NextVisibleColumn != -1) ? &table->Columns[column->NextVisibleColumn] : NULL) if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) - if ((column->IndexWithinActiveSet < table->FreezeColumnsRequest) == (next_column->IndexWithinActiveSet < table->FreezeColumnsRequest)) + if ((column->IndexWithinVisibleSet < table->FreezeColumnsRequest) == (next_column->IndexWithinVisibleSet < table->FreezeColumnsRequest)) table->ReorderColumnDir = +1; } @@ -2257,7 +2258,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->SortOrder != -1 && !column->IsActive) + if (column->SortOrder != -1 && !column->IsVisible) column->SortOrder = -1; if (column->SortOrder == -1) continue; @@ -2300,7 +2301,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (!(column->Flags & ImGuiTableColumnFlags_NoSort) && column->IsActive) + if (!(column->Flags & ImGuiTableColumnFlags_NoSort) && column->IsVisible) { sort_order_count = 1; column->SortOrder = 0; @@ -2398,7 +2399,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->DisplayOrder = column->DisplayOrder; column_settings->SortOrder = column->SortOrder; column_settings->SortDirection = column->SortDirection; - column_settings->Visible = column->IsActive; + column_settings->Visible = column->IsVisible; // We skip saving some data in the .ini file when they are unnecessary to restore our state // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. @@ -2450,7 +2451,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) column->DisplayOrder = column_settings->DisplayOrder; else column->DisplayOrder = (ImS8)column_n; - column->IsActive = column->IsActiveNextFrame = column_settings->Visible; + column->IsVisible = column->IsVisibleNextFrame = column_settings->Visible; column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } @@ -2592,13 +2593,13 @@ void ImGui::DebugNodeTable(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[n]; const char* name = TableGetColumnName(table, n); BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" - "Active: %d, Clipped: %d, DrawChannels: %d,%d\n" + "Visible: %d, Clipped: %d, DrawChannels: %d,%d\n" "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Ideal %d/%d\n" "SortOrder: %d, SortDir: %s\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, - column->IsActive, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, + column->IsVisible, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, column->WidthGiven, column->WidthRequested, column->ResizeWeight, column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersIdeal, column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", From bc170e73257e2be2713f63e0b386e81abca65f64 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 22 May 2020 15:27:53 +0200 Subject: [PATCH 048/144] Tables: Renamed ResizeWeight->WidthStretchWeight, WidthRequested->WidthFixedRequest, clarififications, comments. --- imgui_demo.cpp | 32 +++++++------- imgui_internal.h | 8 ++-- imgui_tables.cpp | 111 ++++++++++++++++++++++++----------------------- 3 files changed, 76 insertions(+), 75 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b91ec0ab..b55d5492 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3246,7 +3246,7 @@ struct MyItem int Quantity; // We have a problem which is affecting _only this demo_ and should not affect your code: - // As we don't rely on std:: or other third-party library to compile dear imgui, we only have reliable access to qsort(), + // As we don't rely on std:: or other third-party library to compile dear imgui, we only have reliable access to qsort(), // however qsort doesn't allow passing user data to comparing function. // As a workaround, we are storing the sort specs in a static/global for the comparing function to access. // In your own use case you would probably pass the sort specs to your sorting/comparing functions directly and not use a global. @@ -3326,9 +3326,9 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Basic")) { // Here we will showcase 4 different ways to output a table. They are very simple variations of a same thing! - + // Basic use of tables using TableNextRow() to create a new row, and TableSetColumnIndex() to select the column. - // In many situations, this is the most flexible and easy to use pattern. + // In many situations, this is the most flexible and easy to use pattern. HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, in a loop."); if (ImGui::BeginTable("##table1", 3)) { @@ -3363,7 +3363,7 @@ static void ShowDemoWindowTables() } // Another subtle variant, we call TableNextCell() _before_ each cell. At the end of a row, TableNextCell() will create a new row. - // Note that we don't call TableNextRow() here! + // Note that we don't call TableNextRow() here! // If we want to call TableNextRow(), then we don't need to call TableNextCell() for the first cell. HelpMarker("Only using TableNextCell(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); if (ImGui::BeginTable("##table4", 3)) @@ -3449,7 +3449,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersV flag as well."); - + if (ImGui::BeginTable("##table1", 3, flags)) { for (int row = 0; row < 5; row++) @@ -3550,7 +3550,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); - + if (ImGui::BeginTable("##table1", 3, flags)) { // Submit columns name with TableSetupColumn() and call TableAutoHeaders() to create a row with a header in each column. @@ -3600,7 +3600,7 @@ static void ShowDemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); - + if (ImGui::BeginTable("##table1", 3, flags, size)) { ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); @@ -3735,7 +3735,7 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Recursive")) { HelpMarker("This demonstrate embedding a table into another table cell."); - + if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersVFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("A0"); @@ -3904,13 +3904,13 @@ static void ShowDemoWindowTables() ImGui::TableAutoHeaders(); // Simple storage to output a dummy file-system. - struct MyTreeNode - { - const char* Name; - const char* Type; - int Size; - int ChildIdx; - int ChildCount; + struct MyTreeNode + { + const char* Name; + const char* Type; + int Size; + int ChildIdx; + int ChildCount; static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes) { ImGui::TableNextRow(); @@ -3960,7 +3960,7 @@ static void ShowDemoWindowTables() } // Demonstrate using TableHeader() calls instead of TableAutoHeaders() - // FIXME-TABLE: Currently this doesn't get us feature-parity with TableAutoHeaders(), e.g. missing context menu. Tables API needs some work! + // FIXME-TABLE: Currently this doesn't get us feature-parity with TableAutoHeaders(), e.g. missing context menu. Tables API needs some work! if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Custom headers")) diff --git a/imgui_internal.h b/imgui_internal.h index d8d05245..362390a9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1897,9 +1897,9 @@ struct ImGuiTableColumn ImGuiTableColumnFlags Flags; // Effective flags. See ImGuiTableColumnFlags_ float MinX; // Absolute positions float MaxX; - float ResizeWeight; // ~1.0f. Master width data when (Flags & _WidthStretch) - float WidthRequested; // Master width data when !(Flags & _WidthStretch) - float WidthGiven; // == (MaxX - MinX). FIXME-TABLE: Store all persistent width in multiple of FontSize? + float WidthStretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. + float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from WidthStretchWeight in TableUpdateLayout() + float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be >WidthRequest to honor minimum width, may be LeftMostStretchedColumnDisplayOrder = -1; table->ColumnsAutoFitWidth = 0.0f; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) @@ -598,27 +598,27 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { // Latch initial size for fixed columns count_fixed += 1; - const bool init_size = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); - if (init_size) + const bool auto_fit = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); + if (auto_fit) { - column->WidthRequested = column_width_ideal; + column->WidthRequest = column_width_ideal; // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very // large height (= first frame scrollbar display very off + clipper would skip lots of items). // This is merely making the side-effect less extreme, but doesn't properly fixes it. if (column->AutoFitQueue > 0x01 && table->IsInitializing) - column->WidthRequested = ImMax(column->WidthRequested, min_column_width * 4.0f); + column->WidthRequest = ImMax(column->WidthRequest, min_column_width * 4.0f); } - width_fixed += column->WidthRequested; + sum_width_fixed_requests += column->WidthRequest; } else { IM_ASSERT(column->Flags & ImGuiTableColumnFlags_WidthStretch); - const int init_size = (column->ResizeWeight < 0.0f); + const int init_size = (column->WidthStretchWeight < 0.0f); if (init_size) - column->ResizeWeight = 1.0f; - total_weights += column->ResizeWeight; + column->WidthStretchWeight = 1.0f; + sum_weights_stretched += column->WidthStretchWeight; if (table->LeftMostStretchedColumnDisplayOrder == -1) table->LeftMostStretchedColumnDisplayOrder = (ImS8)column->DisplayOrder; } @@ -636,7 +636,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; else width_avail = work_rect.GetWidth() - width_spacings - 1.0f; - const float width_avail_for_stretched_columns = width_avail - width_fixed; + const float width_avail_for_stretched_columns = width_avail - sum_width_fixed_requests; float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; // Apply final width based on requested widths @@ -652,12 +652,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Allocate width for stretched/weighted columns if (column->Flags & ImGuiTableColumnFlags_WidthStretch) { - float weight_ratio = column->ResizeWeight / total_weights; - column->WidthRequested = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); - width_remaining_for_stretched_columns -= column->WidthRequested; + // WidthStretchWeight gets converted into WidthRequest + float weight_ratio = column->WidthStretchWeight / sum_weights_stretched; + column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); + width_remaining_for_stretched_columns -= column->WidthRequest; - // [Resize Rule 2] Resizing from right-side of a weighted column before a fixed column froward sizing - // to left-side of fixed column. We also need to copy the NoResize flag.. + // [Resize Rule 2] Resizing from right-side of a weighted column preceding a fixed column + // needs to forward resizing to left-side of fixed column. We also need to copy the NoResize flag.. if (column->NextVisibleColumn != -1) if (ImGuiTableColumn* next_column = &table->Columns[column->NextVisibleColumn]) if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed) @@ -673,7 +674,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) count_resizable++; // Assign final width, record width in case we will need to shrink - column->WidthGiven = ImFloor(ImMax(column->WidthRequested, min_column_width)); + column->WidthGiven = ImFloor(ImMax(column->WidthRequest, min_column_width)); table->ColumnsTotalWidth += column->WidthGiven; } @@ -703,19 +704,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) #endif // Redistribute remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). - // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending where - // the mouse cursor is and/or relative weights. + // Using right-to-left distribution (more likely to match resizing cursor), could be adjusted depending + // on where the mouse cursor is and/or relative weights. // FIXME-TABLE: May be simpler to store floating width and floor final positions only // FIXME-TABLE: Make it optional? User might prefer to preserve pixel perfect same size? if (width_remaining_for_stretched_columns >= 1.0f) - for (int order_n = table->ColumnsCount - 1; total_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) + for (int order_n = table->ColumnsCount - 1; sum_weights_stretched > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) { if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) continue; ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]]; if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; - column->WidthRequested += 1.0f; + column->WidthRequest += 1.0f; column->WidthGiven += 1.0f; width_remaining_for_stretched_columns -= 1.0f; } @@ -1168,8 +1169,8 @@ static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; - visible_weight += column->ResizeWeight; - visible_width += column->WidthRequested; + visible_weight += column->WidthStretchWeight; + visible_width += column->WidthRequest; } IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f); @@ -1179,7 +1180,7 @@ static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; - column->ResizeWeight = (column->WidthRequested + 0.0f) / visible_width; + column->WidthStretchWeight = (column->WidthRequest + 0.0f) / visible_width; } } @@ -1205,7 +1206,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f column_0_width = ImClamp(column_0_width, min_width, max_width_0); // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) - if (column_0->WidthGiven == column_0_width || column_0->WidthRequested == column_0_width) + if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width) return; ImGuiTableColumn* column_1 = (column_0->NextVisibleColumn != -1) ? &table->Columns[column_0->NextVisibleColumn] : NULL; @@ -1241,14 +1242,14 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f if (table->LeftMostStretchedColumnDisplayOrder != -1 && table->LeftMostStretchedColumnDisplayOrder < column_0->DisplayOrder) { // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) - float column_1_width = ImMax(column_1->WidthRequested - (column_0_width - column_0->WidthRequested), min_width); - column_0_width = column_0->WidthRequested + column_1->WidthRequested - column_1_width; - column_1->WidthRequested = column_1_width; + float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + column_1->WidthRequest = column_1_width; } // Apply //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthRequested, column_0_width); - column_0->WidthRequested = column_0_width; + column_0->WidthRequest = column_0_width; } else if (column_0->Flags & ImGuiTableColumnFlags_WidthStretch) { @@ -1257,15 +1258,15 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f { float off = (column_0->WidthGiven - column_0_width); float column_1_width = column_1->WidthGiven + off; - column_1->WidthRequested = ImMax(min_width, column_1_width); + column_1->WidthRequest = ImMax(min_width, column_1_width); return; } // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) - float column_1_width = ImMax(column_1->WidthRequested - (column_0_width - column_0->WidthRequested), min_width); - column_0_width = column_0->WidthRequested + column_1->WidthRequested - column_1_width; - column_1->WidthRequested = column_1_width; - column_0->WidthRequested = column_0_width; + float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + column_1->WidthRequest = column_1_width; + column_0->WidthRequest = column_0_width; TableUpdateColumnsWeightFromWidth(table); } table->IsSettingsDirty = true; @@ -1462,23 +1463,23 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, // Initialize defaults // FIXME-TABLE: We don't restore widths/weight so let's avoid using IsSettingsLoaded for now - if (table->IsInitializing && column->WidthRequested < 0.0f && column->ResizeWeight < 0.0f)// && !table->IsSettingsLoaded) + if (table->IsInitializing && column->WidthRequest < 0.0f && column->WidthStretchWeight < 0.0f)// && !table->IsSettingsLoaded) { // Init width or weight // Disable auto-fit if a default fixed width has been specified if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) { - column->WidthRequested = init_width_or_weight; + column->WidthRequest = init_width_or_weight; column->AutoFitQueue = 0x00; } if (flags & ImGuiTableColumnFlags_WidthStretch) { IM_ASSERT(init_width_or_weight < 0.0f || init_width_or_weight > 0.0f); - column->ResizeWeight = (init_width_or_weight < 0.0f ? 1.0f : init_width_or_weight); + column->WidthStretchWeight = (init_width_or_weight < 0.0f ? 1.0f : init_width_or_weight); } else { - column->ResizeWeight = 1.0f; + column->WidthStretchWeight = 1.0f; } } if (table->IsInitializing) @@ -2594,13 +2595,13 @@ void ImGui::DebugNodeTable(ImGuiTable* table) const char* name = TableGetColumnName(table, n); BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" "Visible: %d, Clipped: %d, DrawChannels: %d,%d\n" - "WidthGiven/Requested: %.1f/%.1f, Weight: %.2f\n" + "WidthGiven/Request: %.1f/%.1f, WidthWeight: %.3f\n" "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Ideal %d/%d\n" "SortOrder: %d, SortDir: %s\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", n, column->DisplayOrder, name ? name : "NULL", column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, column->IsVisible, column->IsClipped, column->DrawChannelRowsBeforeFreeze, column->DrawChannelRowsAfterFreeze, - column->WidthGiven, column->WidthRequested, column->ResizeWeight, + column->WidthGiven, column->WidthRequest, column->WidthStretchWeight, column->ContentWidthRowsFrozen, column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed, column->ContentWidthHeadersIdeal, column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? "Ascending" : (column->SortDirection == ImGuiSortDirection_Descending) ? "Descending" : "None", column->UserID, column->Flags, From af52a0cea203c43385252adb94a44b42c7cd2ff8 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 22 May 2020 16:48:13 +0200 Subject: [PATCH 049/144] Tables: Resizing weighted column preserve sum of weights. Fix ResizedColumn init leading to undesirable TableSetColumnWidth() on first run. Rework TableSettingsHandler_ReadLine() structure to allow other types of line. --- imgui_internal.h | 3 ++- imgui_tables.cpp | 27 +++++++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 362390a9..2acc1d74 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2037,6 +2037,7 @@ struct ImGuiTable LastResizedColumn = -1; ContextPopupColumn = -1; ReorderColumn = -1; + ResizedColumn = -1; } }; @@ -2048,7 +2049,7 @@ struct ImGuiTableColumnSettings ImS8 Index; ImS8 DisplayOrder; ImS8 SortOrder; - ImS8 SortDirection : 7; + ImU8 SortDirection : 2; ImU8 Visible : 1; ImGuiTableColumnSettings() diff --git a/imgui_tables.cpp b/imgui_tables.cpp index aa133d97..9fd8c3d1 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1180,7 +1180,7 @@ static void TableUpdateColumnsWeightFromWidth(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; if (!column->IsVisible || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) continue; - column->WidthStretchWeight = (column->WidthRequest + 0.0f) / visible_width; + column->WidthStretchWeight = ((column->WidthRequest + 0.0f) / visible_width) * visible_weight; } } @@ -2506,18 +2506,21 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; int column_n = 0, r = 0, n = 0; - if (sscanf(line, "Column %d%n", &column_n, &r) == 1) { line = ImStrSkipBlank(line + r); } else { return; } - if (column_n < 0 || column_n >= settings->ColumnsCount) - return; - char c = 0; - ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; - column->Index = (ImS8)column_n; - if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } - if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); /* .. */ settings->SaveFlags |= ImGuiTableFlags_Resizable; } - if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->Visible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } - if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImS8)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } - if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } + if (sscanf(line, "Column %d%n", &column_n, &r) == 1) + { + if (column_n < 0 || column_n >= settings->ColumnsCount) + return; + line = ImStrSkipBlank(line + r); + char c = 0; + ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; + column->Index = (ImS8)column_n; + if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); /* .. */ settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->Visible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } + if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImS8)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } + if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } + } } static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) From 6bc0bbccf3296d7c31be281db9bf9778ac8ffa67 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 22 May 2020 18:00:23 +0200 Subject: [PATCH 050/144] Tables: Restore width/weight saving/loading code. Non-weighted width currently not font/DPI change friendly. --- imgui_internal.h | 6 ++++-- imgui_tables.cpp | 36 ++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 2acc1d74..6deb8f9a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2050,7 +2050,8 @@ struct ImGuiTableColumnSettings ImS8 DisplayOrder; ImS8 SortOrder; ImU8 SortDirection : 2; - ImU8 Visible : 1; + ImU8 IsVisible : 1; + ImU8 IsWeighted : 1; ImGuiTableColumnSettings() { @@ -2059,7 +2060,8 @@ struct ImGuiTableColumnSettings Index = -1; DisplayOrder = SortOrder = -1; SortDirection = ImGuiSortDirection_None; - Visible = 1; + IsVisible = 1; + IsWeighted = 0; } }; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 9fd8c3d1..726a9312 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -73,7 +73,7 @@ // | TableSortSpecsClickColumn() - when clicked: alter sort order and sort direction // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) // - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() -// | TableUpdateLayout() - called by the FIRST call to TableNextRow()! lock all widths and columns positions. +// | TableUpdateLayout() - lock all widths and columns positions! called by the FIRST call to TableNextRow()! // | - TableUpdateDrawChannels() - setup ImDrawList channels // | - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission // | - TableDrawContextMenu() - draw right-click context menu @@ -2395,20 +2395,21 @@ void ImGui::TableSaveSettings(ImGuiTable* table) settings->SaveFlags = ImGuiTableFlags_Resizable; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { - //column_settings->WidthOrWeight = column->WidthRequested; // FIXME-TABLE: Missing + column_settings->WidthOrWeight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->WidthStretchWeight : column->WidthRequest; column_settings->Index = (ImS8)n; column_settings->DisplayOrder = column->DisplayOrder; column_settings->SortOrder = column->SortOrder; column_settings->SortDirection = column->SortDirection; - column_settings->Visible = column->IsVisible; + column_settings->IsVisible = column->IsVisible; + column_settings->IsWeighted = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; // We skip saving some data in the .ini file when they are unnecessary to restore our state // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; - if (column_settings->SortOrder != -1) + if (column->SortOrder != -1) settings->SaveFlags |= ImGuiTableFlags_Sortable; - if (column_settings->Visible != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) + if (column->IsVisible != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) settings->SaveFlags |= ImGuiTableFlags_Hideable; } settings->SaveFlags &= table->Flags; @@ -2446,13 +2447,21 @@ void ImGui::TableLoadSettings(ImGuiTable* table) int column_n = column_settings->Index; if (column_n < 0 || column_n >= table->ColumnsCount) continue; + ImGuiTableColumn* column = &table->Columns[column_n]; - //column->WidthRequested = column_settings->WidthOrWeight; // FIXME-WIP + if (settings->SaveFlags & ImGuiTableFlags_Resizable) + { + if (column_settings->IsWeighted) + column->WidthStretchWeight = column_settings->WidthOrWeight; + else + column->WidthRequest = column_settings->WidthOrWeight; + column->AutoFitQueue = 0x00; + } if (settings->SaveFlags & ImGuiTableFlags_Reorderable) column->DisplayOrder = column_settings->DisplayOrder; else column->DisplayOrder = (ImS8)column_n; - column->IsVisible = column->IsVisibleNextFrame = column_settings->Visible; + column->IsVisible = column->IsVisibleNextFrame = column_settings->IsVisible; column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } @@ -2505,6 +2514,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, { // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; + float f = 0.0f; int column_n = 0, r = 0, n = 0; if (sscanf(line, "Column %d%n", &column_n, &r) == 1) @@ -2516,8 +2526,9 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; column->Index = (ImS8)column_n; if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } - if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); /* .. */ settings->SaveFlags |= ImGuiTableFlags_Resizable; } - if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->Visible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsWeighted = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsWeighted = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsVisible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImS8)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } } @@ -2548,8 +2559,9 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" buf->appendf("Column %-2d", column_n); if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); - if (save_size) buf->appendf(" Width=%d", 0);// (int)settings_column->WidthOrWeight); // FIXME-TABLE - if (save_visible) buf->appendf(" Visible=%d", column->Visible); + if (save_size && column->IsWeighted) buf->appendf(" Weight=%.4f", column->WidthOrWeight); + if (save_size && !column->IsWeighted) buf->appendf(" Width=%d", (int)column->WidthOrWeight); + if (save_visible) buf->appendf(" Visible=%d", column->IsVisible); if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); if (save_sort && column->SortOrder != -1) buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); buf->append("\n"); @@ -2631,7 +2643,7 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) BulletText("Column %d Order %d SortOrder %d %s Visible %d UserID 0x%08X WidthOrWeight %.3f", n, column_settings->DisplayOrder, column_settings->SortOrder, (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---", - column_settings->Visible, column_settings->UserID, column_settings->WidthOrWeight); + column_settings->IsVisible, column_settings->UserID, column_settings->WidthOrWeight); } TreePop(); } From fec9d7d2260abeb6ced684cabb0e70599101c87b Mon Sep 17 00:00:00 2001 From: omar Date: Sun, 24 May 2020 20:07:21 +0200 Subject: [PATCH 051/144] Tables: Rescale fixed widths when font size change to support varying dpi scale at runtime and on .ini reload. --- imgui_internal.h | 2 ++ imgui_tables.cpp | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/imgui_internal.h b/imgui_internal.h index 6deb8f9a..780bc33c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1985,6 +1985,7 @@ struct ImGuiTable float ColumnsTotalWidth; // Sum of current column width float ColumnsAutoFitWidth; // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window float ResizedColumnNextWidth; + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. ImRect OuterRect; // Note: OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). ImRect WorkRect; ImRect InnerClipRect; @@ -2070,6 +2071,7 @@ struct ImGuiTableSettings { ImGuiID ID; // Set to 0 to invalidate/delete the setting ImGuiTableFlags SaveFlags; // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..) + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. ImS8 ColumnsCount; ImS8 ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 726a9312..87bffa86 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -328,6 +328,21 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->IsSettingsRequestLoad) TableLoadSettings(table); + // Handle DPI/font resize + // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. + // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. + // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. + // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. + const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? + if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) + { + const float scale_factor = new_ref_scale_unit / table->RefScale; + //IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor); + for (int n = 0; n < columns_count; n++) + table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; + } + table->RefScale = new_ref_scale_unit; + // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. @@ -607,6 +622,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very // large height (= first frame scrollbar display very off + clipper would skip lots of items). // This is merely making the side-effect less extreme, but doesn't properly fixes it. + // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? if (column->AutoFitQueue > 0x01 && table->IsInitializing) column->WidthRequest = ImMax(column->WidthRequest, min_column_width * 4.0f); } @@ -2392,6 +2408,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); // FIXME-TABLE: Logic to avoid saving default widths? + bool save_ref_scale = false; settings->SaveFlags = ImGuiTableFlags_Resizable; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { @@ -2402,6 +2419,8 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->SortDirection = column->SortDirection; column_settings->IsVisible = column->IsVisible; column_settings->IsWeighted = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; + if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) + save_ref_scale = true; // We skip saving some data in the .ini file when they are unnecessary to restore our state // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. @@ -2413,6 +2432,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) settings->SaveFlags |= ImGuiTableFlags_Hideable; } settings->SaveFlags &= table->Flags; + settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; MarkIniSettingsDirty(); } @@ -2438,6 +2458,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) settings = TableGetBoundSettings(table); } table->SettingsLoadedFlags = settings->SaveFlags; + table->RefScale = settings->RefScale; IM_ASSERT(settings->ColumnsCount == table->ColumnsCount); // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn @@ -2517,6 +2538,8 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, float f = 0.0f; int column_n = 0, r = 0, n = 0; + if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } + if (sscanf(line, "Column %d%n", &column_n, &r) == 1) { if (column_n < 0 || column_n >= settings->ColumnsCount) @@ -2553,6 +2576,8 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); + if (settings->RefScale != 0.0f) + buf->appendf("RefScale=%g\n", settings->RefScale); ImGuiTableColumnSettings* column = settings->GetColumnSettings(); for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) { @@ -2610,7 +2635,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) const char* name = TableGetColumnName(table, n); BulletText("Column %d order %d name '%s': +%.1f to +%.1f\n" "Visible: %d, Clipped: %d, DrawChannels: %d,%d\n" - "WidthGiven/Request: %.1f/%.1f, WidthWeight: %.3f\n" + "WidthGiven/Request: %.2f/%.2f, WidthWeight: %.3f\n" "ContentWidth: RowsFrozen %d, RowsUnfrozen %d, HeadersUsed/Ideal %d/%d\n" "SortOrder: %d, SortDir: %s\n" "UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..", From e41fc7b5b03e6efccad996552b06b6bb1c467890 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 29 May 2020 18:32:04 +0200 Subject: [PATCH 052/144] Tables: Fix TableDrawMergeChannels() mistakenly merging unfrozen columns cliprect with host cliprect. Comments, debug. --- imgui_tables.cpp | 62 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 87bffa86..2c3deeea 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1290,11 +1290,11 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // Columns where the contents didn't stray off their local clip rectangle can be merged into a same draw command. // To achieve this we merge their clip rect and make them contiguous in the channel list so they can be merged. -// So here we'll reorder the draw cmd which can be merged, by arranging them into a maximum of 4 distinct groups: +// This function first reorder the draw cmd which can be merged, by arranging them into a maximum of 4 distinct groups: // // 1 group: 2 groups: 2 groups: 4 groups: -// [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze -// [ .. ] or no scroll [ 1. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll +// [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 02 ] row+col freeze +// [ .. ] or no scroll [ 1. ] and v-scroll [ .. ] and h-scroll [ 13 ] and v+h-scroll // // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group @@ -1306,8 +1306,9 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). // Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds // matches, by e.g. calling SetCursorScreenPos(). -// - The channel uses more than one draw command itself. We drop all our merging stuff here.. we could do better -// but it's going to be rare. +// - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here.. +// we could do better but it's going to be rare and probably not worth the hassle. +// Columns for which the draw chnanel(s) haven't been merged with other will use their own ImDrawCmd. // // This function is particularly tricky to understand.. take a breath. void ImGui::TableDrawMergeChannels(ImGuiTable* table) @@ -1350,15 +1351,18 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) continue; // Find out the width of this merge group and check if it will fit in our column. - float width_contents; - if (merge_group_sub_count == 1) // No row freeze (same as testing !is_frozen_v) - width_contents = ImMax(column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed); - else if (merge_group_sub_n == 0) // Row freeze: use width before freeze - width_contents = ImMax(column->ContentWidthRowsFrozen, column->ContentWidthHeadersUsed); - else // Row freeze: use width after freeze - width_contents = column->ContentWidthRowsUnfrozen; - if (width_contents > column->WidthGiven && !(column->Flags & ImGuiTableColumnFlags_NoClipX)) - continue; + if (!(column->Flags & ImGuiTableColumnFlags_NoClipX)) + { + float width_contents; + if (merge_group_sub_count == 1) // No row freeze (same as testing !is_frozen_v) + width_contents = ImMax(column->ContentWidthRowsUnfrozen, column->ContentWidthHeadersUsed); + else if (merge_group_sub_n == 0) // Row freeze: use width before freeze + width_contents = ImMax(column->ContentWidthRowsFrozen, column->ContentWidthHeadersUsed); + else // Row freeze: use width after freeze + width_contents = column->ContentWidthRowsUnfrozen; + if (width_contents > column->WidthGiven) + continue; + } const int merge_group_dst_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); @@ -1385,28 +1389,50 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) } // Invalidate current draw channel - // (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging) + // (we don't clear DrawChannelBeforeRowFreeze/DrawChannelAfterRowFreeze solely to facilitate debugging/later inspection of data) column->DrawChannelCurrent = -1; } + // [DEBUG] Display merge groups +#if 0 + if (g.IO.KeyShift) + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + { + MergeGroup* merge_group = &merge_groups[merge_group_n]; + if (merge_group->ChannelsCount == 0) + continue; + char buf[32]; + ImFormatString(buf, 32, "MG%d:%d", merge_group_n, merge_group->ChannelsCount); + ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4); + ImVec2 text_size = CalcTextSize(buf, NULL); + GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255)); + GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL); + GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255)); + } +#endif + // 2. Rewrite channel list in our preferred order if (merge_group_mask != 0) { + // Conceptually we want to test if only 1 bit of merge_group_mask is set, but with no freezing we know it's always going to be group 3. + // We need to test for !is_frozen because any unfitting column will not be part of a merge group, so testing for merge_group_mask isn't enough. + const bool may_extend_clip_rect_to_host_rect = (merge_group_mask == (1 << 3)) && !is_frozen_v && !is_frozen_h; + // Use shared temporary storage so the allocation gets amortized g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - 1); ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; - ImBitArray remaining_mask; + ImBitArray remaining_mask; // We need 130-bit of storage remaining_mask.ClearBits(); remaining_mask.SetBitRange(1, splitter->_Count - 1); // Background channel 0 not part of the merge (see channel allocation in TableUpdateDrawChannels) int remaining_count = splitter->_Count - 1; - const bool may_extend_clip_rect_to_host_rect = ImIsPowerOfTwo(merge_group_mask); - for (int merge_group_n = 0; merge_group_n < 4; merge_group_n++) + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) { MergeGroup* merge_group = &merge_groups[merge_group_n]; ImRect merge_clip_rect = merge_groups[merge_group_n].ClipRect; if (may_extend_clip_rect_to_host_rect) { + IM_ASSERT(merge_group_n == 3); //GetOverlayDrawList()->AddRect(table->HostClipRect.Min, table->HostClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 3.0f); //GetOverlayDrawList()->AddRect(table->InnerClipRect.Min, table->InnerClipRect.Max, IM_COL32(0, 255, 0, 200), 0.0f, ~0, 1.0f); //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 2.0f); From 7513842284e513f19d87da7aeb5945d619020ad5 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 29 May 2020 19:31:47 +0200 Subject: [PATCH 053/144] Tables: Fix assert/crash when a visible column is clipped in a multi clip group situation. --- imgui_tables.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2c3deeea..efeed39f 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1331,11 +1331,10 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) bool merge_groups_all_fit_within_inner_rect = (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0; // 1. Scan channels and take note of those which can be merged - for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { - if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + if (!(table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n))) continue; - const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; const int merge_group_sub_count = is_frozen_v ? 2 : 1; From af992d1321b2f2c9ea5dbfdeddad8ad68e9b0306 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2020 16:03:50 +0200 Subject: [PATCH 054/144] Tables: Tweak settings functions to more prominently clarify the two levels of function. --- imgui.cpp | 2 +- imgui_internal.h | 11 +++++++---- imgui_tables.cpp | 22 ++++++++++++++-------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7ad7a547..bc1ac4b7 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3970,7 +3970,7 @@ void ImGui::Initialize(ImGuiContext* context) #ifdef IMGUI_HAS_TABLE // Add .ini handle for ImGuiTable type - TableInstallSettingsHandler(context); + TableSettingsInstallHandler(context); #endif // #ifdef IMGUI_HAS_TABLE #ifdef IMGUI_HAS_DOCK diff --git a/imgui_internal.h b/imgui_internal.h index 780bc33c..b9e1d889 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2275,10 +2275,13 @@ namespace ImGui IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); - IMGUI_API void TableLoadSettings(ImGuiTable* table); - IMGUI_API void TableSaveSettings(ImGuiTable* table); - IMGUI_API ImGuiTableSettings* TableGetBoundSettings(const ImGuiTable* table); - IMGUI_API void TableInstallSettingsHandler(ImGuiContext* context); + IMGUI_API void TableSettingsInstallHandler(ImGuiContext* context); + IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); + IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); + IMGUI_API void TableSettingsClearByID(ImGuiID id); + IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableSaveSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); // Tab Bars IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index efeed39f..48ef06a7 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2376,7 +2376,7 @@ static void InitTableSettings(ImGuiTableSettings* settings, ImGuiID id, int colu settings->WantApply = true; } -static ImGuiTableSettings* CreateTableSettings(ImGuiID id, int columns_count) +ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) { ImGuiContext& g = *GImGui; ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings)); @@ -2385,7 +2385,7 @@ static ImGuiTableSettings* CreateTableSettings(ImGuiID id, int columns_count) } // Find existing settings -static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id) +ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) { // FIXME-OPT: Might want to store a lookup map for this? ImGuiContext& g = *GImGui; @@ -2395,8 +2395,14 @@ static ImGuiTableSettings* FindTableSettingsByID(ImGuiID id) return NULL; } +void ImGui::TableSettingsClearByID(ImGuiID id) +{ + if (ImGuiTableSettings* settings = TableSettingsFindByID(id)) + settings->ID = 0; +} + // Get settings for a given table, NULL if none -ImGuiTableSettings* ImGui::TableGetBoundSettings(const ImGuiTable* table) +ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) { if (table->SettingsOffset != -1) { @@ -2421,7 +2427,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) ImGuiTableSettings* settings = TableGetBoundSettings(table); if (settings == NULL) { - settings = CreateTableSettings(table->ID, table->ColumnsCount); + settings = TableSettingsCreate(table->ID, table->ColumnsCount); table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); } settings->ColumnsCount = (ImS8)table->ColumnsCount; @@ -2473,7 +2479,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) ImGuiTableSettings* settings; if (table->SettingsOffset == -1) { - settings = FindTableSettingsByID(table->ID); + settings = TableSettingsFindByID(table->ID); if (settings == NULL) return; table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); @@ -2544,7 +2550,7 @@ static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) return NULL; - if (ImGuiTableSettings* settings = FindTableSettingsByID(id)) + if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id)) { if (settings->ColumnsCountMax >= columns_count) { @@ -2553,7 +2559,7 @@ static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, } settings->ID = 0; // Invalidate storage if we won't fit because of a count change } - return CreateTableSettings(id, columns_count); + return ImGui::TableSettingsCreate(id, columns_count); } static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) @@ -2620,7 +2626,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle } } -void ImGui::TableInstallSettingsHandler(ImGuiContext* context) +void ImGui::TableSettingsInstallHandler(ImGuiContext* context) { ImGuiContext& g = *context; ImGuiSettingsHandler ini_handler; From 8690d1f9cec767878a97d01dd8d723b26a00bc36 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Mon, 1 Jun 2020 12:25:08 +0300 Subject: [PATCH 055/144] Tables: Fix sort specs sometimes incorrectly reporting sort spec count when table loses _MultiSortable flag during runtime. --- imgui_tables.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 48ef06a7..3fec1061 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2330,6 +2330,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. if (need_fix_single_sort_order) { + sort_order_count = 1; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) if (column_n != column_with_smallest_sort_order) table->Columns[column_n].SortOrder = -1; From dc915c86ca8172c2836db676088f2da80b802866 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2020 16:33:06 +0200 Subject: [PATCH 056/144] Tables: Fixed a manual resize path not marking settings as dirty, TableSortSpecsSanitize() doesn't need to test table->IsInitializing --- imgui_tables.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3fec1061..15287cd2 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1247,9 +1247,9 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // Rules: // - [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). - // - [Resize Rule 2] Resizing from right-side of a Stretch column before a fixed column froward sizing to left-side of fixed column. + // - [Resize Rule 2] Resizing from right-side of a Stretch column before a fixed column forward sizing to left-side of fixed column. // - [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure that our left border won't move. - + table->IsSettingsDirty = true; if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) { // [Resize Rule 3] If we are are followed by a fixed column and we have a Stretch column before, we need to ensure @@ -1285,7 +1285,6 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f column_0->WidthRequest = column_0_width; TableUpdateColumnsWeightFromWidth(table); } - table->IsSettingsDirty = true; } // Columns where the contents didn't stray off their local clip rectangle can be merged into a same draw command. @@ -2294,6 +2293,8 @@ bool ImGui::TableGetColumnIsSorted(int column_n) void ImGui::TableSortSpecsSanitize(ImGuiTable* table) { + IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); + // Clear SortOrder from hidden column and verify that there's no gap or duplicate. int sort_order_count = 0; ImU64 sort_order_mask = 0x00; @@ -2339,8 +2340,8 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) } } - // Fallback default sort order (if no column has the ImGuiTableColumnFlags_DefaultSort flag) - if (sort_order_count == 0 && table->IsInitializing) + // Fallback default sort order (if no column had the ImGuiTableColumnFlags_DefaultSort flag) + if (sort_order_count == 0) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; From 58411f27cba505d1f4959ff3cde857f118a09911 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 3 Jun 2020 19:42:43 +0200 Subject: [PATCH 057/144] Tables: Avoid TableGetSortSpecs() having a side-effect on sort specs sanitization. --- imgui_internal.h | 4 ++- imgui_tables.cpp | 75 ++++++++++++++++++++++++++++-------------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index b9e1d889..0112685d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1938,7 +1938,7 @@ struct ImGuiTableColumn PrevVisibleColumn = NextVisibleColumn = -1; AutoFitQueue = CannotSkipItemsQueue = (1 << 3) - 1; // Skip for three frames SortOrder = -1; - SortDirection = ImGuiSortDirection_Ascending; + SortDirection = ImGuiSortDirection_None; } }; @@ -2020,6 +2020,7 @@ struct ImGuiTable bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; bool IsSortSpecsDirty; + bool IsSortSpecsChangedForUser; // Reported to end-user via TableGetSortSpecs()->SpecsChanged and then clear. bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). bool IsSettingsRequestLoad; @@ -2265,6 +2266,7 @@ namespace ImGui IMGUI_API void TableDrawContextMenu(ImGuiTable* table, int column_n); IMGUI_API void TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* column, bool add_to_existing_sort_orders); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); + IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); IMGUI_API void TableBeginRow(ImGuiTable* table); IMGUI_API void TableEndRow(ImGuiTable* table); IMGUI_API void TableBeginCell(ImGuiTable* table, int column_n); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 15287cd2..a34ba34e 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -546,6 +546,10 @@ static ImGuiTableColumnFlags TableFixColumnFlags(ImGuiTable* table, ImGuiTableCo static void TableFixColumnSortDirection(ImGuiTableColumn* column) { + // Initial sort state + if (column->SortDirection == ImGuiSortDirection_None) + column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); + // Handle NoSortAscending/NoSortDescending if (column->SortDirection == ImGuiSortDirection_Ascending && (column->Flags & ImGuiTableColumnFlags_NoSortAscending)) column->SortDirection = ImGuiSortDirection_Descending; @@ -872,6 +876,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 1); else inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); + + // Sanitize and build sort specs before we have a change to use them for display. + // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change) + if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable)) + TableSortSpecsBuild(table); } // Process interaction on resizing borders. Actual size change will be applied in EndTable() @@ -2224,10 +2233,9 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click if (column->SortOrder == -1 || !add_to_existing_sort_orders) column->SortOrder = add_to_existing_sort_orders ? sort_order_max + 1 : 0; } - else + else if (!add_to_existing_sort_orders) { - if (!add_to_existing_sort_orders) - column->SortOrder = -1; + column->SortOrder = -1; } TableFixColumnSortDirection(column); } @@ -2248,34 +2256,11 @@ const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() if (!(table->Flags & ImGuiTableFlags_Sortable)) return NULL; - // Flatten sort specs into user facing data - const bool was_dirty = table->IsSortSpecsDirty; - if (was_dirty) - { - TableSortSpecsSanitize(table); + if (table->IsSortSpecsDirty) + TableSortSpecsBuild(table); - // Write output - table->SortSpecsData.resize(table->SortSpecsCount); - table->SortSpecs.ColumnsMask = 0x00; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - { - ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->SortOrder == -1) - continue; - ImGuiTableSortSpecsColumn* sort_spec = &table->SortSpecsData[column->SortOrder]; - sort_spec->ColumnUserID = column->UserID; - sort_spec->ColumnIndex = (ImU8)column_n; - sort_spec->SortOrder = (ImU8)column->SortOrder; - sort_spec->SortDirection = column->SortDirection; - table->SortSpecs.ColumnsMask |= (ImU64)1 << column_n; - } - } - - // User facing data - table->SortSpecs.Specs = table->SortSpecsData.Data; - table->SortSpecs.SpecsCount = table->SortSpecsData.Size; - table->SortSpecs.SpecsChanged = was_dirty; - table->IsSortSpecsDirty = false; + table->SortSpecs.SpecsChanged = table->IsSortSpecsChangedForUser; + table->IsSortSpecsChangedForUser = false; return table->SortSpecs.SpecsCount ? &table->SortSpecs : NULL; } @@ -2345,10 +2330,11 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { ImGuiTableColumn* column = &table->Columns[column_n]; - if (!(column->Flags & ImGuiTableColumnFlags_NoSort) && column->IsVisible) + if (column->IsVisible && !(column->Flags & ImGuiTableColumnFlags_NoSort)) { sort_order_count = 1; column->SortOrder = 0; + TableFixColumnSortDirection(column); break; } } @@ -2356,6 +2342,33 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) table->SortSpecsCount = (ImS8)sort_order_count; } +void ImGui::TableSortSpecsBuild(ImGuiTable* table) +{ + IM_ASSERT(table->IsSortSpecsDirty); + TableSortSpecsSanitize(table); + + // Write output + table->SortSpecsData.resize(table->SortSpecsCount); + table->SortSpecs.ColumnsMask = 0x00; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (column->SortOrder == -1) + continue; + ImGuiTableSortSpecsColumn* sort_spec = &table->SortSpecsData[column->SortOrder]; + sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnIndex = (ImU8)column_n; + sort_spec->SortOrder = (ImU8)column->SortOrder; + sort_spec->SortDirection = column->SortDirection; + table->SortSpecs.ColumnsMask |= (ImU64)1 << column_n; + } + table->SortSpecs.Specs = table->SortSpecsData.Data; + table->SortSpecs.SpecsCount = table->SortSpecsData.Size; + + table->IsSortSpecsDirty = false; + table->IsSortSpecsChangedForUser = true; +} + //------------------------------------------------------------------------- // TABLE - .ini settings //------------------------------------------------------------------------- From b7416394684572b267ea53ba9c82ac120e17db78 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 10 Jun 2020 11:15:44 +0200 Subject: [PATCH 058/144] Tables: Fix rendering of row bg and line separators (#3293, broken by fixes #3163, code was accidently relying on SetCurrentChannel not updating rectangle) + Used shortcut in PushTableBackground/PopTableBackground --- imgui_tables.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a34ba34e..ba9fcabb 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1316,7 +1316,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // matches, by e.g. calling SetCursorScreenPos(). // - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here.. // we could do better but it's going to be rare and probably not worth the hassle. -// Columns for which the draw chnanel(s) haven't been merged with other will use their own ImDrawCmd. +// Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd. // // This function is particularly tricky to understand.. take a breath. void ImGui::TableDrawMergeChannels(ImGuiTable* table) @@ -1658,7 +1658,10 @@ void ImGui::TableEndRow(ImGuiTable* table) } if (bg_col != 0 || border_col != 0) + { + window->DrawList->_CmdHeader.ClipRect = table->HostClipRect.ToVec4(); table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); + } // Draw background // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle @@ -1896,6 +1899,9 @@ void ImGui::PushTableBackground() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; + + // Set cmd header ahead to avoid SetCurrentChannel+PushClipRect doing an unnecessary AddDrawCmd/Pop + window->DrawList->_CmdHeader.ClipRect = table->HostClipRect.ToVec4(); table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); PushClipRect(table->HostClipRect.Min, table->HostClipRect.Max, false); } @@ -1906,6 +1912,10 @@ void ImGui::PopTableBackground() ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + + // Set cmd header ahead to avoid SetCurrentChannel+PopClipRect doing an unnecessary AddDrawCmd/Pop + ImVec4 pop_clip_rect = window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 2]; + window->DrawList->_CmdHeader.ClipRect = pop_clip_rect; table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); PopClipRect(); } From 363eae94e6ccc73472f841f620ceb242cd20125b Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 13 Jun 2020 17:27:51 +0200 Subject: [PATCH 059/144] Tables: Further fix #3293, #3163 + fixed for row unfreezing border not always showing due to unset clip rect. --- imgui.h | 4 ++-- imgui_demo.cpp | 4 ++-- imgui_internal.h | 1 + imgui_tables.cpp | 46 +++++++++++++++++++--------------------------- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/imgui.h b/imgui.h index 122facf4..3d9898d6 100644 --- a/imgui.h +++ b/imgui.h @@ -1036,8 +1036,8 @@ enum ImGuiTableFlags_ ImGuiTableFlags_BordersVFullHeight = 1 << 11, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. // Padding, Sizing ImGuiTableFlags_NoClipX = 1 << 12, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow) - ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use WidthFixed or WidthAlwaysAutoResize policy. Read description above for more details. - ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use WidthStretch policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b55d5492..fab06d81 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3790,10 +3790,10 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyStretchX); // Can't specify both sizing polices so we clear the other - ImGui::SameLine(); HelpMarker("Default if _ScrollX if disabled."); + ImGui::SameLine(); HelpMarker("Default if _ScrollX if disabled. Makes columns use _WidthStretch policy by default."); if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyFixedX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyFixedX)) flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyFixedX); // Can't specify both sizing polices so we clear the other - ImGui::SameLine(); HelpMarker("Default if _ScrollX if enabled."); + ImGui::SameLine(); HelpMarker("Default if _ScrollX if enabled. Makes columns use _WidthFixed by default, or _WidthAlwaysAutoResize if _Resizable is not set."); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_NoClipX", (unsigned int*)&flags, ImGuiTableFlags_NoClipX); diff --git a/imgui_internal.h b/imgui_internal.h index 0112685d..989570e2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1992,6 +1992,7 @@ struct ImGuiTable ImRect BackgroundClipRect; // We use this to cpu-clip cell background color fill ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. ImRect HostWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImRect HostBackupClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() ImVec2 HostCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() ImGuiWindow* OuterWindow; // Parent window for the table ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index ba9fcabb..297c3ad4 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1626,6 +1626,8 @@ void ImGui::TableEndRow(ImGuiTable* table) const float bg_y1 = table->RowPosY1; const float bg_y2 = table->RowPosY2; + const bool unfreeze_rows = (table->CurrentRow + 1 == table->FreezeRowsCount && table->FreezeRowsCount > 0); + if (table->CurrentRow == 0) table->LastFirstRowHeight = bg_y2 - bg_y1; @@ -1657,8 +1659,11 @@ void ImGui::TableEndRow(ImGuiTable* table) } } - if (bg_col != 0 || border_col != 0) + const bool draw_stong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); + if (bg_col != 0 || border_col != 0 || draw_stong_bottom_border) { + // In theory we could call SetWindowClipRectBeforeChannelChange() but since we know TableEndRow() is + // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. window->DrawList->_CmdHeader.ClipRect = table->HostClipRect.ToVec4(); table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); } @@ -1674,19 +1679,15 @@ void ImGui::TableEndRow(ImGuiTable* table) } // Draw top border - const float border_y = bg_y1; - if (border_col && border_y >= table->BackgroundClipRect.Min.y && border_y < table->BackgroundClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), border_col); + if (border_col && bg_y1 >= table->BackgroundClipRect.Min.y && bg_y1 < table->BackgroundClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col); + + // Draw bottom border at the row unfreezing mark (always strong) + if (draw_stong_bottom_border) + if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong); } - const bool unfreeze_rows = (table->CurrentRow + 1 == table->FreezeRowsCount && table->FreezeRowsCount > 0); - - // Draw bottom border (always strong) - const bool draw_separating_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); - if (draw_separating_border) - if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong); - // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and // get the new cursor position. @@ -1755,15 +1756,8 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) } else { + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); - //window->ClipRect = column->ClipRect; - //IM_ASSERT(column->ClipRect.Max.x > column->ClipRect.Min.x && column->ClipRect.Max.y > column->ClipRect.Min.y); - //window->DrawList->_ClipRectStack.back() = ImVec4(column->ClipRect.Min.x, column->ClipRect.Min.y, column->ClipRect.Max.x, column->ClipRect.Max.y); - //window->DrawList->UpdateClipRect(); - window->DrawList->PopClipRect(); - window->DrawList->PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); - //IMGUI_DEBUG_LOG("%d (%.0f,%.0f)(%.0f,%.0f)\n", column_n, column->ClipRect.Min.x, column->ClipRect.Min.y, column->ClipRect.Max.x, column->ClipRect.Max.y); - window->ClipRect = window->DrawList->_ClipRectStack.back(); } } @@ -1900,10 +1894,10 @@ void ImGui::PushTableBackground() ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; - // Set cmd header ahead to avoid SetCurrentChannel+PushClipRect doing an unnecessary AddDrawCmd/Pop - window->DrawList->_CmdHeader.ClipRect = table->HostClipRect.ToVec4(); + // Optimization: avoid SetCurrentChannel() + PushClipRect() + table->HostBackupClipRect = window->ClipRect; + SetWindowClipRectBeforeSetChannel(window, table->HostClipRect); table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); - PushClipRect(table->HostClipRect.Min, table->HostClipRect.Max, false); } void ImGui::PopTableBackground() @@ -1913,11 +1907,9 @@ void ImGui::PopTableBackground() ImGuiTable* table = g.CurrentTable; ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; - // Set cmd header ahead to avoid SetCurrentChannel+PopClipRect doing an unnecessary AddDrawCmd/Pop - ImVec4 pop_clip_rect = window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 2]; - window->DrawList->_CmdHeader.ClipRect = pop_clip_rect; + // Optimization: avoid PopClipRect() + SetCurrentChannel() + SetWindowClipRectBeforeSetChannel(window, table->HostBackupClipRect); table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); - PopClipRect(); } // Output context menu into current window (generally a popup) From 8530b6af3dfcde4b0653b6ffccb2a34eab5bd7e4 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 13 Jun 2020 18:02:02 +0200 Subject: [PATCH 060/144] Tables: Not flagging whole column as SkipItems based on clipping visibility (breaks layout) --- imgui.h | 2 +- imgui_tables.cpp | 44 +++++++++++++++++++++----------------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/imgui.h b/imgui.h index 3d9898d6..df9cb8d8 100644 --- a/imgui.h +++ b/imgui.h @@ -672,7 +672,7 @@ namespace ImGui // - If you are using tables as a sort of grid, populating every columns with the same type of contents, // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). // - See Demo->Tables for details. - // - See ImGuiTableFlags_ enums for a description of available flags. + // - See ImGuiTableFlags_ and ImGuiTableColumnsFlags_ enums for a description of available flags. #define IMGUI_HAS_TABLE 1 IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 297c3ad4..c7ab20aa 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -753,7 +753,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == visible_n) offset_x += work_rect.Min.x - table->OuterRect.Min.x; - if (!(table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n))) + if ((table->VisibleMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) { // Hidden column: clear a few fields and we are done with it for the remainder of the function. // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. @@ -798,31 +798,27 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.ClipWithFull(host_clip_rect); column->IsClipped = (column->ClipRect.Max.x <= column->ClipRect.Min.x) && (column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0; - column->SkipItems = column->IsClipped || table->HostSkipItems; if (column->IsClipped) - { - // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. - table->VisibleUnclippedMaskByIndex &= ~((ImU64)1 << column_n); - } - else - { - // Starting cursor position - column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; + table->VisibleUnclippedMaskByIndex &= ~((ImU64)1 << column_n); // Columns with the _WidthAlwaysAutoResize sizing policy will never be updated then. - // Alignment - // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in - // many cases (to be able to honor this we might be able to store a log of cells width, per row, for - // visible rows, but nav/programmatic scroll would have visible artifacts.) - //if (column->Flags & ImGuiTableColumnFlags_AlignRight) - // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen); - //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) - // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); + column->SkipItems = !column->IsVisible || table->HostSkipItems; - // Reset content width variables - const float initial_max_pos_x = column->MinX + table->CellPaddingX1; - column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; - column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersIdeal = initial_max_pos_x; - } + // Starting cursor position + column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; + + // Alignment + // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in + // many cases (to be able to honor this we might be able to store a log of cells width, per row, for + // visible rows, but nav/programmatic scroll would have visible artifacts.) + //if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // column->StartXRows = ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen); + //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) + // column->StartXRows = ImLerp(column->StartXRows, ImMax(column->StartXRows, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); + + // Reset content width variables + const float initial_max_pos_x = column->MinX + table->CellPaddingX1; + column->ContentMaxPosRowsFrozen = column->ContentMaxPosRowsUnfrozen = initial_max_pos_x; + column->ContentMaxPosHeadersUsed = column->ContentMaxPosHeadersIdeal = initial_max_pos_x; // Don't decrement auto-fit counters until container window got a chance to submit its items if (table->HostSkipItems == false) @@ -1798,6 +1794,7 @@ bool ImGui::TableNextCell() TableNextRow(); } + // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. int column_n = table->CurrentColumn; return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } @@ -1849,6 +1846,7 @@ bool ImGui::TableSetColumnIndex(int column_idx) TableBeginCell(table, column_idx); } + // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_idx)) != 0; } From 4c4882ffe4bbe8408437a1a8aa8f1520cba76a78 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 15 Jun 2020 15:24:59 +0200 Subject: [PATCH 061/144] Tables: Fixed channel merge when resizing columns with headers. Disable unnecessary and broken merge when using _NoClipX. --- imgui_internal.h | 2 +- imgui_tables.cpp | 73 ++++++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 989570e2..5523f2ca 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2263,8 +2263,8 @@ namespace ImGui IMGUI_API void TableSetColumnWidth(int column_n, float width); IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); IMGUI_API void TableDrawBorders(ImGuiTable* table); - IMGUI_API void TableDrawMergeChannels(ImGuiTable* table); IMGUI_API void TableDrawContextMenu(ImGuiTable* table, int column_n); + IMGUI_API void TableReorderDrawChannelsForMerge(ImGuiTable* table); IMGUI_API void TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* column, bool add_to_existing_sort_orders); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index c7ab20aa..866b0a4c 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -82,7 +82,7 @@ // - [...] user emit contents // - EndTable() user ends the table // | TableDrawBorders() - draw outer borders, inner vertical borders -// | TableDrawMergeChannels() - merge draw channels if clipping isn't required +// | TableReorderDrawChannelsForMerge() - merge draw channels if clipping isn't required // | EndChild() - (if ScrollX/ScrollY is set) //----------------------------------------------------------------------------- @@ -991,9 +991,34 @@ void ImGui::EndTable() if ((flags & ImGuiTableFlags_Borders) != 0) TableDrawBorders(table); + // Store content width reference for each column (before attempting to merge draw calls) + const float backup_outer_cursor_pos_x = outer_window->DC.CursorPos.x; + const float backup_outer_max_pos_x = outer_window->DC.CursorMaxPos.x; + const float backup_inner_max_pos_x = inner_window->DC.CursorMaxPos.x; + float max_pos_x = backup_inner_max_pos_x; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + + // Store content width (for both Headers and Rows) + //float ref_x = column->MinX; + float ref_x_rows = column->StartXRows - table->CellPaddingX1; + float ref_x_headers = column->StartXHeaders - table->CellPaddingX1; + column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); + column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); + column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); + column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); + + // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. + if (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) + max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); + } + // Flatten channels and merge draw calls table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); - TableDrawMergeChannels(table); + if ((table->Flags & ImGuiTableFlags_NoClipX) == 0) + TableReorderDrawChannelsForMerge(table); + table->DrawSplitter.Merge(inner_window->DrawList); // When releasing a column being resized, scroll to keep the resulting column in sight const float min_column_width = TableGetMinColumnWidth(); @@ -1020,9 +1045,6 @@ void ImGui::EndTable() } // Layout in outer window - const float backup_outer_cursor_pos_x = outer_window->DC.CursorPos.x; - const float backup_outer_max_pos_x = outer_window->DC.CursorMaxPos.x; - const float backup_inner_max_pos_x = inner_window->DC.CursorMaxPos.x; inner_window->WorkRect = table->HostWorkRect; inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; @@ -1039,26 +1061,6 @@ void ImGui::EndTable() ItemSize(item_size); } - // Store content width reference for each column - float max_pos_x = backup_inner_max_pos_x; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - { - ImGuiTableColumn* column = &table->Columns[column_n]; - - // Store content width (for both Headers and Rows) - //float ref_x = column->MinX; - float ref_x_rows = column->StartXRows - table->CellPaddingX1; - float ref_x_headers = column->StartXHeaders - table->CellPaddingX1; - column->ContentWidthRowsFrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsFrozen - ref_x_rows); - column->ContentWidthRowsUnfrozen = (ImS16)ImMax(0.0f, column->ContentMaxPosRowsUnfrozen - ref_x_rows); - column->ContentWidthHeadersUsed = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersUsed - ref_x_headers); - column->ContentWidthHeadersIdeal = (ImS16)ImMax(0.0f, column->ContentMaxPosHeadersIdeal - ref_x_headers); - - // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. - if (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) - max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); - } - // Override EndChild/ItemSize max extent with our own to enable auto-resize on the X axis when possible // FIXME-TABLE: This can be improved (e.g. for Fixed columns we don't want to auto AutoFitWidth? or propagate window auto-fit to table?) if (table->Flags & ImGuiTableFlags_ScrollX) @@ -1292,9 +1294,13 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f } } -// Columns where the contents didn't stray off their local clip rectangle can be merged into a same draw command. -// To achieve this we merge their clip rect and make them contiguous in the channel list so they can be merged. -// This function first reorder the draw cmd which can be merged, by arranging them into a maximum of 4 distinct groups: +// This function reorder draw channels based on matching clip rectangle, to facilitate merging them. +// +// Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve +// this we merge their clip rect and make them contiguous in the channel list, so they can be merged +// by the call to DrawSplitter.Merge() following to the call to this function. +// +// We reorder draw commands by arranging them into a maximum of 4 distinct groups: // // 1 group: 2 groups: 2 groups: 4 groups: // [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 02 ] row+col freeze @@ -1303,8 +1309,10 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group // based on its position (within frozen rows/columns groups or not). -// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect, and they will be -// merged by the DrawSplitter.Merge() call. +// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect. +// +// This function assume that each column are pointing to a distinct draw channel, +// otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask. // // Column channels will not be merged into one of the 1-4 groups in the following cases: // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). @@ -1315,7 +1323,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd. // // This function is particularly tricky to understand.. take a breath. -void ImGui::TableDrawMergeChannels(ImGuiTable* table) +void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) { ImGuiContext& g = *GImGui; ImDrawListSplitter* splitter = &table->DrawSplitter; @@ -1472,9 +1480,6 @@ void ImGui::TableDrawMergeChannels(ImGuiTable* table) IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); memcpy(splitter->_Channels.Data + 1, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - 1) * sizeof(ImDrawChannel)); } - - // 3. Actually merge (channels using the same clip rect will be contiguous and naturally merged) - splitter->Merge(table->InnerWindow->DrawList); } // We use a default parameter of 'init_width_or_weight == -1' From 798aed729a0042bc1a544891bd2fde39475efff8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2020 21:15:49 +0200 Subject: [PATCH 062/144] Tables: Added TableGetHoveredColumn(), extracted some context menu code out, simplifying TableAutoHeaders() toward aim of it being a user-land function. --- imgui.h | 1 + imgui_internal.h | 14 +++-- imgui_tables.cpp | 137 ++++++++++++++++++++++++++--------------------- 3 files changed, 87 insertions(+), 65 deletions(-) diff --git a/imgui.h b/imgui.h index df9cb8d8..f1e280f8 100644 --- a/imgui.h +++ b/imgui.h @@ -683,6 +683,7 @@ namespace ImGui IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. + IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. // Tables: Headers & Columns declaration // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu diff --git a/imgui_internal.h b/imgui_internal.h index 5523f2ca..48d50230 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2002,7 +2002,7 @@ struct ImGuiTable ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() ImS8 SortSpecsCount; ImS8 DeclColumnsCount; // Count calls to TableSetupColumn() - ImS8 HoveredColumnBody; // [DEBUG] Unlike HoveredColumnBorder this doesn't fulfill all Hovering rules properly. Used for debugging/tools for now. + ImS8 HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column! ImS8 HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). ImS8 ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. ImS8 LastResizedColumn; // Index of column being resized from previous frame. @@ -2041,6 +2041,7 @@ struct ImGuiTable ContextPopupColumn = -1; ReorderColumn = -1; ResizedColumn = -1; + HoveredColumnBody = HoveredColumnBorder = -1; } }; @@ -2263,7 +2264,8 @@ namespace ImGui IMGUI_API void TableSetColumnWidth(int column_n, float width); IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); IMGUI_API void TableDrawBorders(ImGuiTable* table); - IMGUI_API void TableDrawContextMenu(ImGuiTable* table, int column_n); + IMGUI_API void TableDrawContextMenu(ImGuiTable* table); + IMGUI_API void TableOpenContextMenu(ImGuiTable* table, int column_n); IMGUI_API void TableReorderDrawChannelsForMerge(ImGuiTable* table); IMGUI_API void TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* column, bool add_to_existing_sort_orders); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); @@ -2278,13 +2280,15 @@ namespace ImGui IMGUI_API void TableSetColumnAutofit(ImGuiTable* table, int column_n); IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); + + // Tables - Settings + IMGUI_API void TableLoadSettings(ImGuiTable* table); + IMGUI_API void TableSaveSettings(ImGuiTable* table); + IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); IMGUI_API void TableSettingsInstallHandler(ImGuiContext* context); IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); IMGUI_API void TableSettingsClearByID(ImGuiID id); - IMGUI_API void TableLoadSettings(ImGuiTable* table); - IMGUI_API void TableSaveSettings(ImGuiTable* table); - IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); // Tab Bars IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 866b0a4c..0027b854 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -284,8 +284,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->FreezeColumnsCount = (inner_window->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); table->DeclColumnsCount = 0; - table->HoveredColumnBody = -1; - table->HoveredColumnBorder = -1; table->RightMostVisibleColumn = -1; // Using opaque colors facilitate overlapping elements of the grid @@ -571,8 +569,12 @@ static float TableGetMinColumnWidth() // for WidthAlwaysAutoResize columns? void ImGui::TableUpdateLayout(ImGuiTable* table) { + ImGuiContext& g = *GImGui; IM_ASSERT(table->IsLayoutLocked == false); + table->HoveredColumnBody = -1; + table->HoveredColumnBorder = -1; + // Compute offset, clip rect for the frame // (can't make auto padding larger than what WorkRect knows about so right-alignment matches) const ImRect work_rect = table->WorkRect; @@ -741,6 +743,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) width_remaining_for_stretched_columns -= 1.0f; } + // Detect hovered column + const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table->LastOuterHeight)); + const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0); + // Setup final position, offset and clipping rectangles int visible_n = 0; float offset_x = (table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x; @@ -803,6 +809,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->SkipItems = !column->IsVisible || table->HostSkipItems; + // Detect hovered column + if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x) + table->HoveredColumnBody = (ImS8)column_n; + // Starting cursor position column->StartXRows = column->StartXHeaders = column->MinX + table->CellPaddingX1; @@ -834,6 +844,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) visible_n++; } + // Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) + if (is_hovering_table && table->HoveredColumnBody == -1) + { + float unused_x1 = table->WorkRect.Min.x; + if (table->RightMostVisibleColumn != -1) + unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostVisibleColumn].ClipRect.Max.x); + if (g.IO.MousePos.x >= unused_x1) + table->HoveredColumnBody = (ImS8)table->ColumnsCount; + } + // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, // either because of using _WidthAlwaysAutoResize/_WidthStretch). // This will hide the resizing option from the context menu. @@ -857,7 +877,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { if (BeginPopup("##TableContextMenu")) { - TableDrawContextMenu(table, table->ContextPopupColumn); + TableDrawContextMenu(table); EndPopup(); } else @@ -897,7 +917,6 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) const float hit_y1 = table->OuterRect.Min.y; const float hit_y2_full = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); const float hit_y2 = borders_full_height ? hit_y2_full : (hit_y1 + table->LastFirstRowHeight); - const float mouse_x_hover_body = (g.IO.MousePos.y >= hit_y1 && g.IO.MousePos.y < hit_y2_full) ? g.IO.MousePos.x : FLT_MAX; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { @@ -906,13 +925,6 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; - - // Detect hovered column: - // - we perform an unusually low-level check here.. not using IsMouseHoveringRect() to avoid touch padding. - // - we don't care about the full set of IsItemHovered() feature either. - if (mouse_x_hover_body >= column->MinX && mouse_x_hover_body < column->MaxX) - table->HoveredColumnBody = (ImS8)column_n; - if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) continue; @@ -1798,9 +1810,12 @@ bool ImGui::TableNextCell() { TableNextRow(); } + int column_n = table->CurrentColumn; + + // FIXME-TABLE: Need to clarify if we want to allow IsItemHovered() here + //g.CurrentWindow->DC.LastItemStatusFlags = (column_n == table->HoveredColumn) ? ImGuiItemStatusFlags_HoveredRect : ImGuiItemStatusFlags_None; // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. - int column_n = table->CurrentColumn; return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } @@ -1851,6 +1866,9 @@ bool ImGui::TableSetColumnIndex(int column_idx) TableBeginCell(table, column_idx); } + // FIXME-TABLE: Need to clarify if we want to allow IsItemHovered() here + //g.CurrentWindow->DC.LastItemStatusFlags = (column_n == table->HoveredColumn) ? ImGuiItemStatusFlags_HoveredRect : ImGuiItemStatusFlags_None; + // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_idx)) != 0; } @@ -1917,7 +1935,7 @@ void ImGui::PopTableBackground() // Output context menu into current window (generally a popup) // FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? -void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) +void ImGui::TableDrawContextMenu(ImGuiTable* table) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -1925,7 +1943,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) return; bool want_separator = false; - selected_column_n = ImClamp(selected_column_n, -1, table->ColumnsCount - 1); + const int selected_column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; // Sizing if (table->Flags & ImGuiTableFlags_Resizable) @@ -1983,28 +2001,44 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table, int selected_column_n) } } +// Use -1 to open menu not specific to a given column. +void ImGui::TableOpenContextMenu(ImGuiTable* table, int column_n) +{ + IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount); + if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) + { + table->IsContextPopupOpen = true; + table->ContextPopupColumn = (ImS8)column_n; + table->InstanceInteracted = table->InstanceCurrent; + OpenPopup("##TableContextMenu"); + } +} + // This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). -// The intent is that advanced users willing to create customized headers would not need to use this helper and may -// create their own. However presently this function uses too many internal structures/calls. +// The intent is that advanced users willing to create customized headers would not need to use this helper +// and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets. +// FIXME-TABLE: However presently this function uses too many internal structures/calls. void ImGui::TableAutoHeaders() { + ImGuiStyle& style = ImGui::GetStyle(); + ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); const int columns_count = table->ColumnsCount; // Calculate row height (for the unlikely case that labels may be are multi-line) + float row_y1 = GetCursorScreenPos().y; float row_height = GetTextLineHeight(); for (int column_n = 0; column_n < columns_count; column_n++) if (TableGetColumnIsVisible(column_n)) row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); - row_height += g.Style.CellPadding.y * 2.0f; + row_height += style.CellPadding.y * 2.0f; // Open row TableNextRow(ImGuiTableRowFlags_Headers, row_height); - if (table->HostSkipItems) // Merely an optimization + if (table->HostSkipItems) // Merely an optimization, you may skip in your own code. return; // This for loop is constructed to not make use of internal functions, @@ -2027,64 +2061,35 @@ void ImGui::TableAutoHeaders() Checkbox("##", &b[column_n]); PopStyleVar(); PopID(); - SameLine(0.0f, g.Style.ItemInnerSpacing.x); + SameLine(0.0f, style.ItemInnerSpacing.x); } #endif - // [DEBUG] - //if (g.IO.KeyCtrl) { static char buf[32]; name = buf; ImGuiTableColumn* c = &table->Columns[column_n]; if (c->Flags & ImGuiTableColumnFlags_WidthStretch) ImFormatString(buf, 32, "%.3f>%.1f", c->ResizeWeight, c->WidthGiven); else ImFormatString(buf, 32, "%.1f", c->WidthGiven); } - // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) + // - in your own code you may omit the PushID/PopID all-together, provided you know you know they won't collide + // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. PushID(table->InstanceCurrent * table->ColumnsCount + column_n); TableHeader(name); PopID(); // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden - if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (IsMouseReleased(1) && IsItemHovered()) open_context_popup = column_n; } - // FIXME-TABLE: This is not user-land code any more + need to explain WHY this is here! + // FIXME-TABLE: This is not user-land code any more + need to explain WHY this is here! (added in fa88f023) window->SkipItems = table->HostSkipItems; // Allow opening popup from the right-most section after the last column - // FIXME-TABLE: This is not user-land code any more... perhaps instead we should expose hovered column. - // and allow some sort of row-centric IsItemHovered() for full flexibility? - float unused_x1 = table->WorkRect.Min.x; - if (table->RightMostVisibleColumn != -1) - unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostVisibleColumn].MaxX); - if (unused_x1 < table->WorkRect.Max.x) - { - // FIXME: We inherit ClipRect/SkipItem from last submitted column (active or not), let's temporarily override it. - // Because we don't perform any rendering here we just overwrite window->ClipRect used by logic. - window->ClipRect = table->InnerClipRect; - - ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; - window->DC.CursorPos = ImVec2(unused_x1, table->RowPosY1); - ImVec2 size = ImVec2(table->WorkRect.Max.x - window->DC.CursorPos.x, table->RowPosY2 - table->RowPosY1); - if (size.x > 0.0f && size.y > 0.0f) - { - InvisibleButton("##RemainingSpace", size); - window->DC.CursorPos.y -= g.Style.ItemSpacing.y; - window->DC.CursorMaxPos = backup_cursor_max_pos; // Don't feed back into the width of the Header row - - // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden. - if (IsMouseReleased(1) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - open_context_popup = -1; - } - - window->ClipRect = window->DrawList->_ClipRectStack.back(); - } + // (We don't actually need to ImGuiHoveredFlags_AllowWhenBlockedByPopup because in reality this is generally + // not required anymore.. because popup opening code tends to be reacting on IsMouseReleased() and the click + // would already have closed any other popups!) + if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count && g.IO.MousePos.y >= row_y1 && g.IO.MousePos.y < row_y1 + row_height) + open_context_popup = -1; // Will open a non-column-specific popup. // Open Context Menu if (open_context_popup != INT_MAX) - if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) - { - table->IsContextPopupOpen = true; - table->ContextPopupColumn = (ImS8)open_context_popup; - table->InstanceInteracted = table->InstanceCurrent; - OpenPopup("##TableContextMenu"); - } + TableOpenContextMenu(table, open_context_popup); } // Emit a column header (text + optional sort order) @@ -2281,6 +2286,15 @@ bool ImGui::TableGetColumnIsSorted(int column_n) return (column->SortOrder != -1); } +int ImGui::TableGetHoveredColumn() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return -1; + return (int)table->HoveredColumnBody; +} + void ImGui::TableSortSpecsSanitize(ImGuiTable* table) { IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); @@ -2679,7 +2693,10 @@ void ImGui::DebugNodeTable(ImGuiTable* table) GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); if (!open) return; - BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s, ColumnsWidth: %.1f, AutoFitWidth: %.1f", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "", table->ColumnsTotalWidth, table->ColumnsAutoFitWidth); + BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); + BulletText("ColumnsWidth: %.1f, AutoFitWidth: %.1f", table->ColumnsTotalWidth, table->ColumnsAutoFitWidth); + BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); + BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); for (int n = 0; n < table->ColumnsCount; n++) { ImGuiTableColumn* column = &table->Columns[n]; From 7ca70dcb81c77c37b5e225d476c0fc82256735ae Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2020 15:27:17 +0200 Subject: [PATCH 063/144] Tables: Using same seed ID regardless of when using a child window or not. --- imgui_tables.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 0027b854..8e9cb702 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -216,6 +216,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->OuterRect = outer_rect; table->WorkRect = outer_rect; + // When not using a child window, WorkRect.Max will grow as we append contents. if (use_child_window) { // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent @@ -241,11 +242,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); } - else - { - // WorkRect.Max will grow as we append contents. - PushID(instance_id); - } + + // Push a standardized ID for both child and not-child using tables, equivalent to BeginTable() doing PushID(label) matching + PushOverrideID(instance_id); // Backup a copy of host window members we will modify ImGuiWindow* inner_window = table->InnerWindow; @@ -1057,6 +1056,8 @@ void ImGui::EndTable() } // Layout in outer window + IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); + PopID(); inner_window->WorkRect = table->HostWorkRect; inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; @@ -1067,7 +1068,6 @@ void ImGui::EndTable() } else { - PopID(); ImVec2 item_size = table->OuterRect.GetSize(); item_size.x = table->ColumnsTotalWidth; ItemSize(item_size); From 27e779b3efbad1b20a24c6fac6641431ad8ad3e7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2020 23:40:06 +0200 Subject: [PATCH 064/144] Tables: Removed dubious window->SkipItem assignment in TableAutoHeaders(). --- imgui_tables.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 8e9cb702..1fad48b7 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2023,7 +2023,7 @@ void ImGui::TableAutoHeaders() ImGuiStyle& style = ImGui::GetStyle(); ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; + //ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); const int columns_count = table->ColumnsCount; @@ -2078,7 +2078,7 @@ void ImGui::TableAutoHeaders() } // FIXME-TABLE: This is not user-land code any more + need to explain WHY this is here! (added in fa88f023) - window->SkipItems = table->HostSkipItems; + //window->SkipItems = table->HostSkipItems; // Allow opening popup from the right-most section after the last column // (We don't actually need to ImGuiHoveredFlags_AllowWhenBlockedByPopup because in reality this is generally From 745d8cdb497ca77e03e3d2c4136492756b398bda Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 19 Jun 2020 17:28:06 +0200 Subject: [PATCH 065/144] Tables: Made TableHeader() responsible for opening per-column context menu to move responsibility away from TableAutoHeaders(). --- imgui_demo.cpp | 3 +-- imgui_tables.cpp | 35 ++++++++++++++++------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index fab06d81..35844b88 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3960,13 +3960,12 @@ static void ShowDemoWindowTables() } // Demonstrate using TableHeader() calls instead of TableAutoHeaders() - // FIXME-TABLE: Currently this doesn't get us feature-parity with TableAutoHeaders(), e.g. missing context menu. Tables API needs some work! if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Custom headers")) { const int COLUMNS_COUNT = 3; - if (ImGui::BeginTable("##table1", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("##table1", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { ImGui::TableSetupColumn("Apricot"); ImGui::TableSetupColumn("Banana"); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 1fad48b7..accc75f3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -874,7 +874,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Context menu if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted) { - if (BeginPopup("##TableContextMenu")) + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) { TableDrawContextMenu(table); EndPopup(); @@ -2010,20 +2011,19 @@ void ImGui::TableOpenContextMenu(ImGuiTable* table, int column_n) table->IsContextPopupOpen = true; table->ContextPopupColumn = (ImS8)column_n; table->InstanceInteracted = table->InstanceCurrent; - OpenPopup("##TableContextMenu"); + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + OpenPopupEx(context_menu_id, ImGuiPopupFlags_None); } } // This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). // The intent is that advanced users willing to create customized headers would not need to use this helper // and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets. -// FIXME-TABLE: However presently this function uses too many internal structures/calls. void ImGui::TableAutoHeaders() { ImGuiStyle& style = ImGui::GetStyle(); ImGuiContext& g = *GImGui; - //ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); const int columns_count = table->ColumnsCount; @@ -2043,7 +2043,6 @@ void ImGui::TableAutoHeaders() // This for loop is constructed to not make use of internal functions, // as this is intended to be a base template to copy and build from. - int open_context_popup = INT_MAX; for (int column_n = 0; column_n < columns_count; column_n++) { if (!TableSetColumnIndex(column_n)) @@ -2071,25 +2070,19 @@ void ImGui::TableAutoHeaders() PushID(table->InstanceCurrent * table->ColumnsCount + column_n); TableHeader(name); PopID(); - - // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden - if (IsMouseReleased(1) && IsItemHovered()) - open_context_popup = column_n; } // FIXME-TABLE: This is not user-land code any more + need to explain WHY this is here! (added in fa88f023) //window->SkipItems = table->HostSkipItems; - // Allow opening popup from the right-most section after the last column - // (We don't actually need to ImGuiHoveredFlags_AllowWhenBlockedByPopup because in reality this is generally - // not required anymore.. because popup opening code tends to be reacting on IsMouseReleased() and the click - // would already have closed any other popups!) - if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count && g.IO.MousePos.y >= row_y1 && g.IO.MousePos.y < row_y1 + row_height) - open_context_popup = -1; // Will open a non-column-specific popup. - - // Open Context Menu - if (open_context_popup != INT_MAX) - TableOpenContextMenu(table, open_context_popup); + // Allow opening popup from the right-most section after the last column. + // (We don't actually need to use ImGuiHoveredFlags_AllowWhenBlockedByPopup because in reality this is generally + // not required anymore.. because popup opening code tends to be reacting on IsMouseReleased() and the click would + // already have closed any other popups!) + // FIXME-TABLE: TableOpenContextMenu() is not public yet. + if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) + if (g.IO.MousePos.y >= row_y1 && g.IO.MousePos.y < row_y1 + row_height) + TableOpenContextMenu(table, -1); // Will open a non-column-specific popup. } // Emit a column header (text + optional sort order) @@ -2214,6 +2207,10 @@ void ImGui::TableHeader(const char* label) column->ContentMaxPosHeadersIdeal = ImMax(column->ContentMaxPosHeadersIdeal, max_pos_x); PopID(); + + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden + if (IsMouseReleased(1) && IsItemHovered()) + TableOpenContextMenu(table, column_n); } void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* clicked_column, bool add_to_existing_sort_orders) From 353bb68e904cab6dc324105bef778e0731dc3934 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 19 Jun 2020 15:39:56 +0200 Subject: [PATCH 066/144] Tables: Demo custom per-popup popups, demonstrate TableGetHoveredColumn() and ImGuiPopupFlags_NoOpenOverExistingPopup. --- imgui_demo.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 35844b88..9f789b40 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4006,6 +4006,70 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + // Demonstrate creating custom context menus inside columns, while playing it nice with context menus provided by TableHeader()/TableAutoHeaders() + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Context menus")) + { + HelpMarker("By default, TableHeader()/TableAutoHeaders() will open a context-menu on right-click."); + ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders; + const int COLUMNS_COUNT = 3; + if (ImGui::BeginTable("##table1", COLUMNS_COUNT, flags)) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + + // Context Menu 1: right-click on header (including empty section after the third column!) should open Default Table Popup + ImGui::TableAutoHeaders(); + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < COLUMNS_COUNT; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::PushID(row * COLUMNS_COUNT + column); + ImGui::Text("Cell %d,%d", column, row); + ImGui::SameLine(); + + // Context Menu 2: right-click on buttons open Custom Button Popup + ImGui::SmallButton(".."); + if (ImGui::BeginPopupContextItem()) + { + ImGui::Text("This is the popup for Button() On Cell %d,%d", column, row); + ImGui::Selectable("Close"); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + } + + // Context Menu 3: Right-click anywhere in columns opens a custom popup + // We use the ImGuiPopupFlags_NoOpenOverExistingPopup flag to avoid displaying over either the standard TableHeader context-menu or the Button context-menu. + const int hovered_column = ImGui::TableGetHoveredColumn(); + for (int column = 0; column < COLUMNS_COUNT + 1; column++) + { + ImGui::PushID(column); + if (hovered_column == column && ImGui::IsMouseReleased(1)) + ImGui::OpenPopup("MyPopup", ImGuiPopupFlags_NoOpenOverExistingPopup); + if (ImGui::BeginPopup("MyPopup")) + { + if (column == COLUMNS_COUNT) + ImGui::Text("This is the popup for unused space after the last column."); + else + ImGui::Text("This is the popup for Column '%s'", ImGui::TableGetColumnName(column)); + ImGui::Selectable("Close"); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + + ImGui::EndTable(); + ImGui::Text("TableGetHoveredColumn() returned: %d", hovered_column); + } + ImGui::TreePop(); + } + static const char* template_items_names[] = { "Banana", "Apple", "Cherry", "Watermelon", "Grapefruit", "Strawberry", "Mango", From 03c8bfaf23d2ec18b969abdfba6383ae89b6fe81 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 2 Jul 2020 13:59:30 +0200 Subject: [PATCH 067/144] Tables: Removed extra +1.0f pixels initially allocated to make right-most column visible, fix visible padding asymmetry. Tweaked debug code in demo. Seems visible enough without. Whole thing is/was fishy, may return to it but right cleaning up seems viable. --- imgui_demo.cpp | 47 +++++++++++++++++++++++++++-------------------- imgui_tables.cpp | 14 +++++++------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 9f789b40..391ec248 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3416,22 +3416,27 @@ static void ShowDemoWindowTables() for (int column = 0; column < 3; column++) { ImGui::TableSetColumnIndex(column); + char buf[32]; if (display_width) { + // [DEBUG] Draw limits ImVec2 p = ImGui::GetCursorScreenPos(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - float x1 = p.x; - float x2 = ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x; - float x3 = draw_list->GetClipRectMax().x; - float y2 = p.y + ImGui::GetTextLineHeight(); - draw_list->AddLine(ImVec2(x1, y2), ImVec2(x3, y2), IM_COL32(255, 255, 0, 255)); // Hard clipping limit - draw_list->AddLine(ImVec2(x1, y2), ImVec2(x2, y2), IM_COL32(255, 0, 0, 255)); // Normal limit - ImGui::Text("w=%.2f", x2 - x1); + float contents_x1 = p.x; + float contents_x2 = ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x; + float cliprect_x1 = ImGui::GetWindowDrawList()->GetClipRectMin().x; + float cliprect_x2 = ImGui::GetWindowDrawList()->GetClipRectMax().x; + float y1 = p.y; + float y2 = p.y + ImGui::GetTextLineHeight() + 2.0f; + ImDrawList* fg_draw_list = ImGui::GetForegroundDrawList(); + fg_draw_list->AddRect(ImVec2(contents_x1, y1 + 0.0f), ImVec2(contents_x2, y2 + 0.0f), IM_COL32(255, 0, 0, 255)); // Contents limit (e.g. Cell + Padding) + fg_draw_list->AddLine(ImVec2(cliprect_x1, y2 + 1.0f), ImVec2(cliprect_x2, y2 + 1.0f), IM_COL32(255, 255, 0, 255)); // Hard clipping limit + sprintf(buf, "w=%.2f", contents_x2 - contents_x1); } else { - ImGui::Text("Hello %d,%d", row, column); + sprintf(buf, "Hello %d,%d", row, column); } + ImGui::TextUnformatted(buf); } } ImGui::EndTable(); @@ -3776,10 +3781,10 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Sizing policies, cell contents")) { HelpMarker("This section allows you to interact and see the effect of StretchX vs FixedX sizing policies depending on whether Scroll is enabled and the contents of your columns."); - enum ContentsType { CT_ShortText, CT_LongText, CT_Button, CT_StretchButton, CT_InputText }; - static int contents_type = CT_StretchButton; + enum ContentsType { CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText }; + static int contents_type = CT_FillButton; ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12); - ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Stretch Button\0InputText\0"); + ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); @@ -3810,11 +3815,11 @@ static void ShowDemoWindowTables() sprintf(label, "Hello %d,%d", row, column); switch (contents_type) { - case CT_ShortText: ImGui::TextUnformatted(label); break; - case CT_LongText: ImGui::Text("Some longer text %d,%d\nOver two lines..", row, column); break; - case CT_Button: ImGui::Button(label); break; - case CT_StretchButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; - case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); break; + case CT_ShortText: ImGui::TextUnformatted(label); break; + case CT_LongText: ImGui::Text("Some longer text %d,%d\nOver two lines..", row, column); break; + case CT_Button: ImGui::Button(label); break; + case CT_FillButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break; + case CT_InputText: ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); break; } } } @@ -4163,9 +4168,9 @@ static void ShowDemoWindowTables() | ImGuiTableFlags_SizingPolicyFixedX ; - enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_Selectable }; - static int contents_type = CT_Button; - const char* contents_type_names[] = { "Text", "Button", "SmallButton", "Selectable" }; + enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_FillButton, CT_Selectable }; + static int contents_type = CT_FillButton; + const char* contents_type_names[] = { "Text", "Button", "SmallButton", "FillButton", "Selectable" }; static int items_count = IM_ARRAYSIZE(template_items_names); static ImVec2 outer_size_value = ImVec2(0, 250); @@ -4354,6 +4359,8 @@ static void ShowDemoWindowTables() ImGui::Button(label); else if (contents_type == CT_SmallButton) ImGui::SmallButton(label); + else if (contents_type == CT_FillButton) + ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); else if (contents_type == CT_Selectable) { if (ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap, ImVec2(0, row_min_height))) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index accc75f3..3c0e6f33 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -70,10 +70,11 @@ // | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of weighted columns) from their respective width // - TableSetupColumn() user submit columns details (optional) // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) -// | TableSortSpecsClickColumn() - when clicked: alter sort order and sort direction +// | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction +// | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) // - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() -// | TableUpdateLayout() - lock all widths and columns positions! called by the FIRST call to TableNextRow()! +// | TableUpdateLayout() - lock all widths, columns positions, clipping rectangles. called by the FIRST call to TableNextRow()! // | - TableUpdateDrawChannels() - setup ImDrawList channels // | - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission // | - TableDrawContextMenu() - draw right-click context menu @@ -650,13 +651,12 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->ColumnsAutoFitWidth += spacing_auto_x * (table->ColumnsVisibleCount - 1); // Layout - // Remove -1.0f to cancel out the +1.0f we are doing in EndTable() to make last column line visible const float width_spacings = table->CellSpacingX * (table->ColumnsVisibleCount - 1); float width_avail; if ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) - width_avail = table->InnerClipRect.GetWidth() - width_spacings - 1.0f; + width_avail = table->InnerClipRect.GetWidth() - width_spacings; else - width_avail = work_rect.GetWidth() - width_spacings - 1.0f; + width_avail = work_rect.GetWidth() - width_spacings; const float width_avail_for_stretched_columns = width_avail - sum_width_fixed_requests; float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; @@ -1023,7 +1023,7 @@ void ImGui::EndTable() // Add an extra 1 pixel so we can see the last column vertical line if it lies on the right-most edge. if (table->VisibleMaskByIndex & ((ImU64)1 << column_n)) - max_pos_x = ImMax(max_pos_x, column->MaxX + 1.0f); + max_pos_x = ImMax(max_pos_x, column->MaxX); } // Flatten channels and merge draw calls @@ -1079,7 +1079,7 @@ void ImGui::EndTable() if (table->Flags & ImGuiTableFlags_ScrollX) { inner_window->DC.CursorMaxPos.x = max_pos_x; // Set contents width for scrolling - outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos_x, backup_outer_cursor_pos_x + table->ColumnsTotalWidth + 1.0f + inner_window->ScrollbarSizes.x); // For auto-fit + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos_x, backup_outer_cursor_pos_x + table->ColumnsTotalWidth + inner_window->ScrollbarSizes.x); // For auto-fit } else { From fcdfde2bc62b9da01bf7e208c303aa4e5a27b57a Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 2 Jul 2020 18:59:02 +0200 Subject: [PATCH 068/144] Tables: Extracted border size into a named variable. --- imgui_tables.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3c0e6f33..2de7fc89 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -88,6 +88,7 @@ //----------------------------------------------------------------------------- // Configuration +static const float TABLE_BORDER_SIZE = 1.0f; // FIXME-TABLE: Currently hard-coded. static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. static const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f; // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. @@ -1110,6 +1111,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) ImDrawList* outer_drawlist = outer_window->DrawList; // Draw inner border and resizing feedback + const float border_size = TABLE_BORDER_SIZE; const float draw_y1 = table->OuterRect.Min.y; float draw_y2_base = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight; float draw_y2_full = table->OuterRect.Max.y; @@ -1125,7 +1127,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) } if ((table->Flags & ImGuiTableFlags_BordersVOuter) && (table->InnerWindow == table->OuterWindow)) - inner_drawlist->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, 1.0f); + inner_drawlist->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, border_size); if (table->Flags & ImGuiTableFlags_BordersVInner) { @@ -1154,7 +1156,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) float draw_y2 = draw_y2_base; if (is_hovered || is_resized || (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1)) draw_y2 = draw_y2_full; - inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, 1.0f); + inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); } } } @@ -1172,16 +1174,18 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if (inner_window != outer_window) outer_border.Expand(1.0f); if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) - outer_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col); + { + outer_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, ~0, border_size); + } else if (table->Flags & ImGuiTableFlags_BordersVOuter) { - outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col); - outer_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col); + outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); + outer_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); } else if (table->Flags & ImGuiTableFlags_BordersHOuter) { - outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col); - outer_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col); + outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); + outer_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); } } if ((table->Flags & ImGuiTableFlags_BordersHInner) && table->RowPosY2 < table->OuterRect.Max.y) @@ -1189,7 +1193,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw bottom-most row border const float border_y = table->RowPosY2; if (border_y >= table->BackgroundClipRect.Min.y && border_y < table->BackgroundClipRect.Max.y) - inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight); + inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); } } @@ -1656,6 +1660,7 @@ void ImGui::TableEndRow(ImGuiTable* table) // Decide of top border color ImU32 border_col = 0; + const float border_size = TABLE_BORDER_SIZE; if (table->CurrentRow != 0 || table->InnerWindow == table->OuterWindow) { if (table->Flags & ImGuiTableFlags_BordersHInner) @@ -1694,12 +1699,12 @@ void ImGui::TableEndRow(ImGuiTable* table) // Draw top border if (border_col && bg_y1 >= table->BackgroundClipRect.Min.y && bg_y1 < table->BackgroundClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col); + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size); // Draw bottom border at the row unfreezing mark (always strong) if (draw_stong_bottom_border) if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) - window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong); + window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); } // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) From 57916b891bdeacd44f084335e9780d1e5b44e5f8 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 2 Jul 2020 21:41:29 +0200 Subject: [PATCH 069/144] Tables: Simplified TableHeader() and not relying on Selectable(), fixed various padding issues. Added work-around for CellRect.Min.x offset by CellSpacing.x. --- imgui_tables.cpp | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2de7fc89..98f18176 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2117,25 +2117,27 @@ void ImGui::TableHeader(const char* label) // If we already got a row height, there's use that. ImRect cell_r = TableGetCellRect(); + cell_r.Min.x -= table->CellSpacingX; // FIXME-TABLE: TableGetCellRect() is misleading. float label_height = ImMax(label_size.y, table->RowMinHeight - g.Style.CellPadding.y * 2.0f); //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] - ImRect work_r = cell_r; - work_r.Min.x = window->DC.CursorPos.x; - work_r.Max.y = work_r.Min.y + label_height; - float ellipsis_max = work_r.Max.x; - - // Selectable - PushID(label); - - // FIXME-TABLE: Fix when padding are disabled. - //window->DC.CursorPos.x = column->MinX + table->CellPadding.x; // Keep header highlighted when context menu is open. // (FIXME-TABLE: however we cannot assume the ID of said popup if it has been created by the user...) const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); - const bool pressed = Selectable("", selected, ImGuiSelectableFlags_DrawHoveredWhenHeld | ImGuiSelectableFlags_DontClosePopups, ImVec2(0.0f, label_height)); - const bool held = IsItemActive(); + ImGuiID id = window->GetID(label); + ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f)); + if (!ItemAdd(bb, id)) + return; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + if (hovered || selected) + { + const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + } if (held) table->HeldHeaderColumn = (ImS8)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; @@ -2164,6 +2166,7 @@ void ImGui::TableHeader(const char* label) // Sort order arrow float w_arrow = 0.0f; float w_sort_text = 0.0f; + float ellipsis_max = cell_r.Max.x; if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) { const float ARROW_SCALE = 0.65f; @@ -2179,7 +2182,7 @@ void ImGui::TableHeader(const char* label) w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; } - float x = ImMax(cell_r.Min.x, work_r.Max.x - w_arrow - w_sort_text); + float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text); ellipsis_max -= w_arrow + w_sort_text; float y = label_pos.y; @@ -2208,11 +2211,9 @@ void ImGui::TableHeader(const char* label) // for merging. // FIXME-TABLE: Clarify policies of how label width and potential decorations (arrows) fit into auto-resize of the column float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; - column->ContentMaxPosHeadersUsed = ImMax(column->ContentMaxPosHeadersUsed, work_r.Max.x);// ImMin(max_pos_x, work_r.Max.x)); + column->ContentMaxPosHeadersUsed = ImMax(column->ContentMaxPosHeadersUsed, cell_r.Max.x);// ImMin(max_pos_x, cell_r.Max.x)); column->ContentMaxPosHeadersIdeal = ImMax(column->ContentMaxPosHeadersIdeal, max_pos_x); - PopID(); - // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden if (IsMouseReleased(1) && IsItemHovered()) TableOpenContextMenu(table, column_n); From c96c84b6dcac2aaea8c63fc71336f93ae9dc3e27 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 17 Jul 2020 22:39:28 +0200 Subject: [PATCH 070/144] Tables: Store submitted column width and avoid saving default default widths. --- imgui_internal.h | 1 + imgui_tables.cpp | 32 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 48d50230..0f2d0c68 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1897,6 +1897,7 @@ struct ImGuiTableColumn ImGuiTableColumnFlags Flags; // Effective flags. See ImGuiTableColumnFlags_ float MinX; // Absolute positions float MaxX; + float WidthOrWeightInitValue; // Value passed to TableSetupColumn() float WidthStretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from WidthStretchWeight in TableUpdateLayout() float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be >WidthRequest to honor minimum width, may be Flags; // Initialize defaults - // FIXME-TABLE: We don't restore widths/weight so let's avoid using IsSettingsLoaded for now - if (table->IsInitializing && column->WidthRequest < 0.0f && column->WidthStretchWeight < 0.0f)// && !table->IsSettingsLoaded) + if (flags & ImGuiTableColumnFlags_WidthStretch) + { + IM_ASSERT(init_width_or_weight != 0.0f && "Need to provide a valid weight!"); + if (init_width_or_weight < 0.0f) + init_width_or_weight = 1.0f; + } + column->WidthOrWeightInitValue = init_width_or_weight; + if (table->IsInitializing && column->WidthRequest < 0.0f && column->WidthStretchWeight < 0.0f) { // Init width or weight - // Disable auto-fit if a default fixed width has been specified if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) { + // Disable auto-fit if a default fixed width has been specified column->WidthRequest = init_width_or_weight; column->AutoFitQueue = 0x00; } if (flags & ImGuiTableColumnFlags_WidthStretch) - { - IM_ASSERT(init_width_or_weight < 0.0f || init_width_or_weight > 0.0f); - column->WidthStretchWeight = (init_width_or_weight < 0.0f ? 1.0f : init_width_or_weight); - } + column->WidthStretchWeight = init_width_or_weight; else - { column->WidthStretchWeight = 1.0f; - } } if (table->IsInitializing) { @@ -2087,7 +2088,7 @@ void ImGui::TableAutoHeaders() // FIXME-TABLE: TableOpenContextMenu() is not public yet. if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) if (g.IO.MousePos.y >= row_y1 && g.IO.MousePos.y < row_y1 + row_height) - TableOpenContextMenu(table, -1); // Will open a non-column-specific popup. + TableOpenContextMenu(table, -1); // Will open a non-column-specific popup. } // Emit a column header (text + optional sort order) @@ -2475,12 +2476,12 @@ void ImGui::TableSaveSettings(ImGuiTable* table) ImGuiTableColumn* column = table->Columns.Data; ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); - // FIXME-TABLE: Logic to avoid saving default widths? bool save_ref_scale = false; - settings->SaveFlags = ImGuiTableFlags_Resizable; + settings->SaveFlags = ImGuiTableFlags_None; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) { - column_settings->WidthOrWeight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->WidthStretchWeight : column->WidthRequest; + const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->WidthStretchWeight : column->WidthRequest; + column_settings->WidthOrWeight = width_or_weight; column_settings->Index = (ImS8)n; column_settings->DisplayOrder = column->DisplayOrder; column_settings->SortOrder = column->SortOrder; @@ -2490,8 +2491,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table) if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) save_ref_scale = true; - // We skip saving some data in the .ini file when they are unnecessary to restore our state + // We skip saving some data in the .ini file when they are unnecessary to restore our state. + // Note that fixed width where initial width was derived from auto-fit will always be saved as WidthOrWeightInitValue will be 0.0f. // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. + if (width_or_weight != column->WidthOrWeightInitValue) + settings->SaveFlags |= ImGuiTableFlags_Resizable; if (column->DisplayOrder != n) settings->SaveFlags |= ImGuiTableFlags_Reorderable; if (column->SortOrder != -1) From a0e6aa1766f161054efc7b421cfa8b3029cca1d1 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 17 Jul 2020 23:23:04 +0200 Subject: [PATCH 071/144] Tables: Fix calculation of auto-fit (remove padding). Demo setting a width in columns setup + ImGuiTableFlags_NoKeepColumnsVisible. --- imgui.h | 2 +- imgui_demo.cpp | 28 ++++++++++++++++++++++++++++ imgui_tables.cpp | 9 ++++----- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/imgui.h b/imgui.h index f1e280f8..c72757ad 100644 --- a/imgui.h +++ b/imgui.h @@ -1041,7 +1041,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) - ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small. + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. // Scrolling ImGuiTableFlags_ScrollX = 1 << 18, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. ImGuiTableFlags_ScrollY = 1 << 19, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 391ec248..2d8fa1a0 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3596,6 +3596,32 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Explicit widths")) + { + static ImGuiTableFlags flags = ImGuiTableFlags_None; + ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", (unsigned int*)&flags, ImGuiTableFlags_NoKeepColumnsVisible); + if (ImGui::BeginTable("##table1", 3, flags)) + { + // We could also set ImGuiTableFlags_SizingPolicyFixedX on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + for (int row = 0; row < 5; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", row, column); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Vertical scrolling, with clipping")) @@ -4223,6 +4249,8 @@ static void ShowDemoWindowTables() ImGui::SameLine(); HelpMarker("[Default if ScrollX is on]\nEnlarge as needed: enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rectangle. Only Fixed columns allowed. Stretched columns will calculate their width assuming no scrolling."); ImGui::CheckboxFlags("ImGuiTableFlags_NoHeadersWidth", (unsigned int*)&flags, ImGuiTableFlags_NoHeadersWidth); ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", (unsigned int*)&flags, ImGuiTableFlags_NoHostExtendY); + ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", (unsigned int*)&flags, ImGuiTableFlags_NoKeepColumnsVisible); + ImGui::SameLine(); HelpMarker("Only available if ScrollX is disabled."); ImGui::Unindent(); ImGui::BulletText("Scrolling:"); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index ab5c1803..2ead3fd8 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -580,7 +580,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // (can't make auto padding larger than what WorkRect knows about so right-alignment matches) const ImRect work_rect = table->WorkRect; const float padding_auto_x = table->CellPaddingX2; - const float spacing_auto_x = table->CellSpacingX * (1.0f + 2.0f); // CellSpacingX is >0.0f when there's no vertical border, in which case we add two extra CellSpacingX to make auto-fit look nice instead of cramped. We may want to expose this somehow. const float min_column_width = TableGetMinColumnWidth(); int count_fixed = 0; @@ -614,7 +613,11 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) column_width_ideal = ImMax(column_width_ideal, column_content_width_headers); column_width_ideal = ImMax(column_width_ideal + padding_auto_x, min_column_width); + + // CellSpacingX is >0.0f when there's no vertical border table->ColumnsAutoFitWidth += column_width_ideal; + if (column->PrevVisibleColumn != -1) + table->ColumnsAutoFitWidth += table->CellSpacingX; if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) { @@ -647,10 +650,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } } - // CellSpacingX is >0.0f when there's no vertical border, in which case we add two extra CellSpacingX to make auto-fit look nice instead of cramped. - // We may want to expose this somehow. - table->ColumnsAutoFitWidth += spacing_auto_x * (table->ColumnsVisibleCount - 1); - // Layout const float width_spacings = table->CellSpacingX * (table->ColumnsVisibleCount - 1); float width_avail; From 30e21eb2805e90039e3f628705910056b13d398f Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 21 Jul 2020 14:36:31 +0200 Subject: [PATCH 072/144] Tables: non-resizable columns also submit their requested width for auto-fit, --- imgui_tables.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 2ead3fd8..4167fd49 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -614,6 +614,12 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column_width_ideal = ImMax(column_width_ideal, column_content_width_headers); column_width_ideal = ImMax(column_width_ideal + padding_auto_x, min_column_width); + // Non-resizable columns also submit their requested width + if (column->Flags & ImGuiTableColumnFlags_WidthFixed) + if (column->WidthOrWeightInitValue > 0.0f) + if (!(table->Flags & ImGuiTableFlags_Resizable) || !(column->Flags & ImGuiTableColumnFlags_NoResize)) + column_width_ideal = ImMax(column_width_ideal, column->WidthOrWeightInitValue); + // CellSpacingX is >0.0f when there's no vertical border table->ColumnsAutoFitWidth += column_width_ideal; if (column->PrevVisibleColumn != -1) From 0847373b98c4bc2ced43531b9b52634df573091e Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 27 Jul 2020 21:45:38 +0200 Subject: [PATCH 073/144] Tables: Comments on Sizing Policies + Rename border V/H flags HInner -> InnerH + offset every flags by two. --- imgui.h | 70 ++++++++++++++++++++++++++++-------------------- imgui_demo.cpp | 36 ++++++++++++------------- imgui_internal.h | 4 +-- imgui_tables.cpp | 48 ++++++++++++++++----------------- 4 files changed, 85 insertions(+), 73 deletions(-) diff --git a/imgui.h b/imgui.h index c72757ad..b3a70ae2 100644 --- a/imgui.h +++ b/imgui.h @@ -1009,10 +1009,22 @@ enum ImGuiTabItemFlags_ }; // Flags for ImGui::BeginTable() -// - Columns can either varying resizing policy: "Fixed", "Stretch" or "AlwaysAutoResize". Toggling ScrollX needs to alter default sizing policy. -// - Sizing policy have many subtle side effects which may be hard to fully comprehend at first.. They'll eventually make sense. -// - with SizingPolicyFixedX (default is ScrollX is on): Columns can be enlarged as needed. Enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rect. Only Fixed columns allowed. Weighted columns will calculate their width assuming no scrolling. -// - with SizingPolicyStretchX (default is ScrollX is off): Fit all columns within available table width (so it doesn't make sense to use ScrollX with Stretch columns!). Fixed and Weighted columns allowed. +// - Important! Sizing policies have particularly complex and subtle side effects, more so than you would expect. +// Read comments/demos carefully + experiment with live demos to get acquainted with them. +// - The default sizing policy for columns depends on whether the ScrollX flag is set on the table: +// When ScrollX is off: +// - Table defaults to ImGuiTableFlags_SizingPolicyStretchX -> all Columns defaults to ImGuiTableColumnFlags_WidthStretch. +// - Columns sizing policy allowed: Fixed/Auto or Stretch. +// - Stretch Columns will share the width available in table. +// - Fixed Columns will generally obtain their requested width unless the Table cannot fit them all. +// When ScrollX is on: +// - Table defaults to ImGuiTableFlags_SizingPolicyFixedX -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed. +// - Columns sizing policy allowed: Fixed/Auto mostly! Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable(). +// - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. +// - Stretch Columns, if any, will calculate their width using inner_width, assuming no scrolling (it really doesn't make sense to do otherwise). +// - Mixing up columns with different sizing policy is possible BUT can be tricky and has some side-effects and restrictions. +// (their visible order and the scrolling state have subtle but necessary effects on how they can be manually resized). +// The typical use of mixing sizing policies is to have ScrollX disabled, one or two Stretch Column and many Fixed Columns. enum ImGuiTableFlags_ { // Features @@ -1025,38 +1037,38 @@ enum ImGuiTableFlags_ ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. // Decoration ImGuiTableFlags_RowBg = 1 << 6, // Use ImGuiCol_TableRowBg and ImGuiCol_TableRowBgAlt colors behind each rows. - ImGuiTableFlags_BordersHInner = 1 << 7, // Draw horizontal borders between rows. - ImGuiTableFlags_BordersHOuter = 1 << 8, // Draw horizontal borders at the top and bottom. - ImGuiTableFlags_BordersVInner = 1 << 9, // Draw vertical borders between columns. - ImGuiTableFlags_BordersVOuter = 1 << 10, // Draw vertical borders on the left and right sides. - ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersHInner | ImGuiTableFlags_BordersHOuter, // Draw horizontal borders. - ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersVInner | ImGuiTableFlags_BordersVOuter, // Draw vertical borders. - ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersVInner | ImGuiTableFlags_BordersHInner, // Draw inner borders. - ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersVOuter | ImGuiTableFlags_BordersHOuter, // Draw outer borders. + ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersOuterH = 1 << 8, // Draw horizontal borders at the top and bottom. + ImGuiTableFlags_BordersInnerV = 1 << 9, // Draw vertical borders between columns. + ImGuiTableFlags_BordersOuterV = 1 << 10, // Draw vertical borders on the left and right sides. + ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH, // Draw horizontal borders. + ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV, // Draw vertical borders. + ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. + ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. - ImGuiTableFlags_BordersVFullHeight = 1 << 11, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. + ImGuiTableFlags_BordersFullHeightV = 1 << 11, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. // Padding, Sizing - ImGuiTableFlags_NoClipX = 1 << 12, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow) - ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. - ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. - ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. - ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) - ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. + ImGuiTableFlags_NoClipX = 1 << 14, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow) + ImGuiTableFlags_SizingPolicyFixedX = 1 << 15, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 16, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. + ImGuiTableFlags_NoHeadersWidth = 1 << 17, // Disable header width contribution to automatic width calculation. + ImGuiTableFlags_NoHostExtendY = 1 << 18, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 19, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. // Scrolling - ImGuiTableFlags_ScrollX = 1 << 18, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. - ImGuiTableFlags_ScrollY = 1 << 19, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + ImGuiTableFlags_ScrollX = 1 << 20, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 21, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, - ImGuiTableFlags_ScrollFreezeTopRow = 1 << 20, // We can lock 1 to 3 rows (starting from the top). Use with ScrollY enabled. - ImGuiTableFlags_ScrollFreeze2Rows = 2 << 20, - ImGuiTableFlags_ScrollFreeze3Rows = 3 << 20, - ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << 22, // We can lock 1 to 3 columns (starting from the left). Use with ScrollX enabled. - ImGuiTableFlags_ScrollFreeze2Columns = 2 << 22, - ImGuiTableFlags_ScrollFreeze3Columns = 3 << 22, + ImGuiTableFlags_ScrollFreezeTopRow = 1 << 22, // We can lock 1 to 3 rows (starting from the top). Use with ScrollY enabled. + ImGuiTableFlags_ScrollFreeze2Rows = 2 << 22, + ImGuiTableFlags_ScrollFreeze3Rows = 3 << 22, + ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << 24, // We can lock 1 to 3 columns (starting from the left). Use with ScrollX enabled. + ImGuiTableFlags_ScrollFreeze2Columns = 2 << 24, + ImGuiTableFlags_ScrollFreeze3Columns = 3 << 24, // [Internal] Combinations and masks ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX, - ImGuiTableFlags_ScrollFreezeRowsShift_ = 20, - ImGuiTableFlags_ScrollFreezeColumnsShift_ = 22, + ImGuiTableFlags_ScrollFreezeRowsShift_ = 22, + ImGuiTableFlags_ScrollFreezeColumnsShift_ = 24, ImGuiTableFlags_ScrollFreezeRowsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeRowsShift_, ImGuiTableFlags_ScrollFreezeColumnsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeColumnsShift_ }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 2d8fa1a0..f38bf4a8 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3388,19 +3388,19 @@ static void ShowDemoWindowTables() static bool display_width = false; ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&flags, ImGuiTableFlags_Borders); - ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersVInner\n | ImGuiTableFlags_BordersVOuter\n | ImGuiTableFlags_BordersHInner\n | ImGuiTableFlags_BordersHOuter"); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH"); ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); ImGui::Indent(); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersHOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersHOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); ImGui::Unindent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); ImGui::Indent(); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersVOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVInner", (unsigned int*)&flags, ImGuiTableFlags_BordersVInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); ImGui::Unindent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); @@ -3476,7 +3476,7 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Resizable, fixed")) { // Here we use ImGuiTableFlags_SizingPolicyFixedX (even though _ScrollX is not set) - // So columns will adopt the "Fixed" policy and will maintain a fixed weight regardless of the whole available width. + // So columns will adopt the "Fixed" policy and will maintain a fixed width regardless of the whole available width (unless table is small) // If there is not enough available width to fit all columns, they will however be resized down. // FIXME-TABLE: Providing a stretch-on-init would make sense especially for tables which don't have saved settings HelpMarker("Using _Resizable + _SizingPolicyFixedX flags.\nFixed-width columns generally makes more sense if you want to use horizontal scrolling."); @@ -3767,7 +3767,7 @@ static void ShowDemoWindowTables() { HelpMarker("This demonstrate embedding a table into another table cell."); - if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersVFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeightV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("A0"); ImGui::TableSetupColumn("A1"); @@ -3776,7 +3776,7 @@ static void ShowDemoWindowTables() ImGui::TableNextRow(); ImGui::Text("A0 Cell 0"); { float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; - if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersVFullHeight | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeightV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("B0"); ImGui::TableSetupColumn("B1"); @@ -3813,10 +3813,10 @@ static void ShowDemoWindowTables() ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; - ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersHOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersHOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVInner", (unsigned int*)&flags, ImGuiTableFlags_BordersVInner); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersVOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) @@ -3922,7 +3922,7 @@ static void ShowDemoWindowTables() ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Tree view")) { - static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersHOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg; + static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg; //ImGui::CheckboxFlags("ImGuiTableFlags_Scroll", (unsigned int*)&flags, ImGuiTableFlags_Scroll); //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); @@ -4230,12 +4230,12 @@ static void ShowDemoWindowTables() ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersVOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVInner", (unsigned int*)&flags, ImGuiTableFlags_BordersVInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersHOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersHOuter); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersHInner", (unsigned int*)&flags, ImGuiTableFlags_BordersHInner); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersVFullHeight", (unsigned int*)&flags, ImGuiTableFlags_BordersVFullHeight); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersFullHeightV", (unsigned int*)&flags, ImGuiTableFlags_BordersFullHeightV); ImGui::Unindent(); ImGui::BulletText("Padding, Sizing:"); diff --git a/imgui_internal.h b/imgui_internal.h index 0f2d0c68..4dc5f0f2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2056,7 +2056,7 @@ struct ImGuiTableColumnSettings ImS8 SortOrder; ImU8 SortDirection : 2; ImU8 IsVisible : 1; - ImU8 IsWeighted : 1; + ImU8 IsStretch : 1; ImGuiTableColumnSettings() { @@ -2066,7 +2066,7 @@ struct ImGuiTableColumnSettings DisplayOrder = SortOrder = -1; SortDirection = ImGuiSortDirection_None; IsVisible = 1; - IsWeighted = 0; + IsStretch = 0; } }; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 4167fd49..22acdf26 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -67,7 +67,7 @@ // | BeginChild() - (if ScrollX/ScrollY is set) // | TableBeginUpdateColumns() - apply resize/order requests, lock columns active state, order // | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) -// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of weighted columns) from their respective width +// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width // - TableSetupColumn() user submit columns details (optional) // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) // | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction @@ -109,7 +109,7 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) // Adjust flags: enforce borders when resizable if (flags & ImGuiTableFlags_Resizable) - flags |= ImGuiTableFlags_BordersVInner; + flags |= ImGuiTableFlags_BordersInnerV; // Adjust flags: disable top rows freezing if there's no scrolling. // We could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. @@ -258,11 +258,11 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Borders // - None ........Content..... Pad .....Content........ - // - VOuter | Pad ..Content..... Pad .....Content.. Pad | // FIXME-TABLE: Not handled properly - // - VInner ........Content.. Pad | Pad ..Content........ // FIXME-TABLE: Not handled properly - // - VOuter+VInner | Pad ..Content.. Pad | Pad ..Content.. Pad | + // - OuterV | Pad ..Content..... Pad .....Content.. Pad | // FIXME-TABLE: Not handled properly + // - InnerV ........Content.. Pad | Pad ..Content........ // FIXME-TABLE: Not handled properly + // - OuterV+InnerV | Pad ..Content.. Pad | Pad ..Content.. Pad | - const bool has_cell_padding_x = (flags & ImGuiTableFlags_BordersVOuter) != 0; + const bool has_cell_padding_x = (flags & ImGuiTableFlags_BordersOuterV) != 0; table->CellPaddingX1 = has_cell_padding_x ? g.Style.CellPadding.x + 1.0f : 0.0f; table->CellPaddingX2 = has_cell_padding_x ? g.Style.CellPadding.x : 0.0f; table->CellPaddingY = g.Style.CellPadding.y; @@ -513,7 +513,7 @@ void ImGui::TableUpdateDrawChannels(ImGuiTable* table) } } -// Adjust flags: default width mode + weighted columns are not allowed when auto extending +// Adjust flags: default width mode + stretch columns are not allowed when auto extending static ImGuiTableColumnFlags TableFixColumnFlags(ImGuiTable* table, ImGuiTableColumnFlags flags) { // Sizing Policy @@ -684,7 +684,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, min_column_width) + 0.01f); width_remaining_for_stretched_columns -= column->WidthRequest; - // [Resize Rule 2] Resizing from right-side of a weighted column preceding a fixed column + // [Resize Rule 2] Resizing from right-side of a stretch column preceding a fixed column // needs to forward resizing to left-side of fixed column. We also need to copy the NoResize flag.. if (column->NextVisibleColumn != -1) if (ImGuiTableColumn* next_column = &table->Columns[column->NextVisibleColumn]) @@ -918,7 +918,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). // Actual columns highlight/render will be performed in EndTable() and not be affected. - const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersVFullHeight); + const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersFullHeightV); const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; const float hit_y1 = table->OuterRect.Min.y; const float hit_y2_full = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); @@ -943,7 +943,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); if (pressed && IsMouseDoubleClicked(0) && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) { - // FIXME-TABLE: Double-clicking on column edge could auto-fit weighted column? + // FIXME-TABLE: Double-clicking on column edge could auto-fit Stretch column? TableSetColumnAutofit(table, column_n); ClearActiveID(); held = hovered = false; @@ -1121,7 +1121,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) float draw_y2_base = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight; float draw_y2_full = table->OuterRect.Max.y; ImU32 border_base_col; - if (!table->IsUsingHeaders || (table->Flags & ImGuiTableFlags_BordersVFullHeight)) + if (!table->IsUsingHeaders || (table->Flags & ImGuiTableFlags_BordersFullHeightV)) { draw_y2_base = draw_y2_full; border_base_col = table->BorderColorLight; @@ -1131,10 +1131,10 @@ void ImGui::TableDrawBorders(ImGuiTable* table) border_base_col = table->BorderColorStrong; } - if ((table->Flags & ImGuiTableFlags_BordersVOuter) && (table->InnerWindow == table->OuterWindow)) + if ((table->Flags & ImGuiTableFlags_BordersOuterV) && (table->InnerWindow == table->OuterWindow)) inner_drawlist->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, border_size); - if (table->Flags & ImGuiTableFlags_BordersVInner) + if (table->Flags & ImGuiTableFlags_BordersInnerV) { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { @@ -1182,18 +1182,18 @@ void ImGui::TableDrawBorders(ImGuiTable* table) { outer_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, ~0, border_size); } - else if (table->Flags & ImGuiTableFlags_BordersVOuter) + else if (table->Flags & ImGuiTableFlags_BordersOuterV) { outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); outer_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); } - else if (table->Flags & ImGuiTableFlags_BordersHOuter) + else if (table->Flags & ImGuiTableFlags_BordersOuterH) { outer_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); outer_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); } } - if ((table->Flags & ImGuiTableFlags_BordersHInner) && table->RowPosY2 < table->OuterRect.Max.y) + if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) { // Draw bottom-most row border const float border_y = table->RowPosY2; @@ -1262,7 +1262,7 @@ void ImGui::TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column_0, f // Scenarios: // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset. // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered. - // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Weighted column will always be minimal size. + // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Stretch column will always be minimal size. // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) // - W1 W2 W3 resize from W1| or W2| --> FIXME // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) @@ -1669,7 +1669,7 @@ void ImGui::TableEndRow(ImGuiTable* table) const float border_size = TABLE_BORDER_SIZE; if (table->CurrentRow != 0 || table->InnerWindow == table->OuterWindow) { - if (table->Flags & ImGuiTableFlags_BordersHInner) + if (table->Flags & ImGuiTableFlags_BordersInnerH) { //if (table->CurrentRow == 0 && table->InnerWindow == table->OuterWindow) // border_col = table->BorderOuterColor; @@ -2492,7 +2492,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->SortOrder = column->SortOrder; column_settings->SortDirection = column->SortDirection; column_settings->IsVisible = column->IsVisible; - column_settings->IsWeighted = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; + column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) save_ref_scale = true; @@ -2549,7 +2549,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) ImGuiTableColumn* column = &table->Columns[column_n]; if (settings->SaveFlags & ImGuiTableFlags_Resizable) { - if (column_settings->IsWeighted) + if (column_settings->IsStretch) column->WidthStretchWeight = column_settings->WidthOrWeight; else column->WidthRequest = column_settings->WidthOrWeight; @@ -2626,8 +2626,8 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n; column->Index = (ImS8)column_n; if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; } - if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsWeighted = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } - if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsWeighted = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; } + if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; } if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsVisible = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; } if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImS8)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; } if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImS8)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } @@ -2661,8 +2661,8 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" buf->appendf("Column %-2d", column_n); if (column->UserID != 0) buf->appendf(" UserID=%08X", column->UserID); - if (save_size && column->IsWeighted) buf->appendf(" Weight=%.4f", column->WidthOrWeight); - if (save_size && !column->IsWeighted) buf->appendf(" Width=%d", (int)column->WidthOrWeight); + if (save_size && column->IsStretch) buf->appendf(" Weight=%.4f", column->WidthOrWeight); + if (save_size && !column->IsStretch) buf->appendf(" Width=%d", (int)column->WidthOrWeight); if (save_visible) buf->appendf(" Visible=%d", column->IsVisible); if (save_order) buf->appendf(" Order=%d", column->DisplayOrder); if (save_sort && column->SortOrder != -1) buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); From b6405a291d361d9b12ffb674970cc10c7ddbb077 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Jul 2020 15:27:22 +0200 Subject: [PATCH 074/144] Tables: Fixed TableHeader() not declaring its height properly. Do NOT declare width. --- imgui_tables.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 22acdf26..cb7a1b34 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2133,6 +2133,7 @@ void ImGui::TableHeader(const char* label) const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); ImGuiID id = window->GetID(label); ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f)); + ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal if (!ItemAdd(bb, id)) return; From 9d8b40414a30fa1d036540269e3196fff186e4b8 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 28 Jul 2020 15:53:14 +0200 Subject: [PATCH 075/144] Tables: Added TableSetBgColor() api with color for RowBg and CellBg colors. --- imgui.h | 23 +++++++++++- imgui_demo.cpp | 62 ++++++++++++++++++++++++++++--- imgui_internal.h | 16 ++++++-- imgui_tables.cpp | 97 +++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 172 insertions(+), 26 deletions(-) diff --git a/imgui.h b/imgui.h index b3a70ae2..ebbfa771 100644 --- a/imgui.h +++ b/imgui.h @@ -157,6 +157,7 @@ typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling +typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect(), AddRectFilled() etc. typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build @@ -684,6 +685,7 @@ namespace ImGui IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. + IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. // Tables: Headers & Columns declaration // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu @@ -1036,7 +1038,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_MultiSortable = 1 << 4, // Allow sorting on multiple columns by holding Shift (sort_specs_count may be > 1). Call TableGetSortSpecs() to obtain sort specs. ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. // Decoration - ImGuiTableFlags_RowBg = 1 << 6, // Use ImGuiCol_TableRowBg and ImGuiCol_TableRowBgAlt colors behind each rows. + ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent to calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. ImGuiTableFlags_BordersOuterH = 1 << 8, // Draw horizontal borders at the top and bottom. ImGuiTableFlags_BordersInnerV = 1 << 9, // Draw vertical borders between columns. @@ -1109,6 +1111,25 @@ enum ImGuiTableRowFlags_ ImGuiTableRowFlags_Headers = 1 << 0 // Identify header row (set default background color + width of its contents accounted different for auto column width) }; +// Enum for ImGui::TableSetBgColor() +// Background colors are rendering in 3 layers: +// - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. +// - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. +// - Layer 2: draw with CellBg color if set. +// The purpose of the two row/columns layers is to let you decide if a background color changes should override or blend with the existing color. +// When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows. +// If you set the color of RowBg0 target, your color will override the existing RowBg0 color. +// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. +enum ImGuiTableBgTarget_ +{ + ImGuiTableBgTarget_None = 0, + ImGuiTableBgTarget_ColumnBg0 = 1, // FIXME-TABLE: Todo. Set column background color 0 (generally used for background + ImGuiTableBgTarget_ColumnBg1 = 2, // FIXME-TABLE: Todo. Set column background color 1 (generally used for selection marking) + ImGuiTableBgTarget_RowBg0 = 3, // Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) + ImGuiTableBgTarget_RowBg1 = 4, // Set row background color 1 (generally used for selection marking) + ImGuiTableBgTarget_CellBg = 5 // Set cell background color (top-most color) +}; + // Flags for ImGui::IsWindowFocused() enum ImGuiFocusedFlags_ { diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f38bf4a8..08005cbd 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3902,14 +3902,11 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Row height")) { HelpMarker("You can pass a 'min_row_height' to TableNextRow().\n\nRows are padded with 'style.CellPadding.y' on top and bottom, so effectively the minimum row height will always be >= 'style.CellPadding.y * 2.0f'.\n\nWe cannot honor a _maximum_ row height as that would requires a unique clipping rectangle per row."); - if (ImGui::BeginTable("##2ways", 2, ImGuiTableFlags_Borders)) + if (ImGui::BeginTable("##Table", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerV)) { - float min_row_height = ImGui::GetFontSize() + ImGui::GetStyle().CellPadding.y * 2.0f; - ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); - ImGui::Text("min_row_height = %.2f", min_row_height); for (int row = 0; row < 10; row++) { - min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); + float min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); ImGui::Text("min_row_height = %.2f", min_row_height); } @@ -3918,6 +3915,61 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + if (ImGui::TreeNode("Background color")) + { + static ImGuiTableFlags table_flags = ImGuiTableFlags_RowBg; + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&table_flags, ImGuiTableFlags_Borders); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&table_flags, ImGuiTableFlags_RowBg); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style."); + + static int row_bg_type = 1; + static int row_bg_target = 1; + static int cell_bg_type = 1; + ImGui::Combo("row bg type", (int*)&row_bg_type, "None\0Red\0Gradient\0"); + ImGui::Combo("row bg target", (int*)&row_bg_target, "RowBg0\0RowBg1\0"); ImGui::SameLine(); HelpMarker("Target RowBg0 to override the alternating odd/even colors,\nTarget RowBg1 to blend with them."); + ImGui::Combo("cell bg type", (int*)&cell_bg_type, "None\0Blue\0"); ImGui::SameLine(); HelpMarker("We are colorizing cells to B1->C2 here."); + IM_ASSERT(row_bg_type >= 0 && row_bg_type <= 2); + IM_ASSERT(row_bg_target >= 0 && row_bg_target <= 1); + IM_ASSERT(cell_bg_type >= 0 && cell_bg_type <= 1); + + if (ImGui::BeginTable("##Table", 5, table_flags)) + { + for (int row = 0; row < 6; row++) + { + ImGui::TableNextRow(); + + // Demonstrate setting a row background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBgX, ...)' + // We use a transparent color so we can see the one behind in case our target is RowBg1 and RowBg0 was already targeted by the ImGuiTableFlags_RowBg flag. + if (row_bg_type != 0) + { + ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient? + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color); + } + + // Fill cells + for (int column = 0; column < 5; column++) + { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%c%c", 'A' + row, '0' + column); + + // Change background of Cells B1->C2 + // Demonstrate setting a cell background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ...)' + // (the CellBg color will be blended over the RowBg and ColumnBg colors) + // We can also pass a column number as a third parameter to TableSetBgColor() and do this outside the column loop. + if (row >= 1 && row <= 2 && column >= 1 && column <= 2 && cell_bg_type == 1) + { + ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 0.65f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Tree view")) diff --git a/imgui_internal.h b/imgui_internal.h index 4dc5f0f2..513b7f24 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1883,11 +1883,11 @@ struct ImGuiTabBar #ifdef IMGUI_HAS_TABLE -#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code +#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. #define IMGUI_TABLE_MAX_COLUMNS 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns set in a ImU64. #define IMGUI_TABLE_MAX_DRAW_CHANNELS (2 + 64 * 2) // See TableUpdateDrawChannels() -// [Internal] sizeof() ~ 100 +// [Internal] sizeof() ~ 104 // We use the terminology "Visible" to refer to a column that is not Hidden by user or settings. However it may still be out of view and clipped (see IsClipped). struct ImGuiTableColumn { @@ -1943,6 +1943,14 @@ struct ImGuiTableColumn } }; +// Transient cell data stored per row. +// sizeof() ~ 6 +struct ImGuiTableCellData +{ + ImU32 BgColor; // Actual color + ImS8 Column; // Column number +}; + struct ImGuiTable { ImGuiID ID; @@ -1950,6 +1958,7 @@ struct ImGuiTable ImVector RawData; ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) + ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. ImU64 VisibleMaskByIndex; // Column Index -> IsVisible map (== not hidden by user/api) in a format adequate for iterating column without touching cold data ImU64 VisibleMaskByDisplayOrder; // Column DisplayOrder -> IsVisible map ImU64 VisibleUnclippedMaskByIndex;// Visible and not Clipped, aka "actually visible" "not hidden by some scrolling" @@ -1970,7 +1979,7 @@ struct ImGuiTable ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ ImGuiTableRowFlags LastRowFlags : 16; int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper) - ImU32 RowBgColor; // Request for current row background color + ImU32 RowBgColor[2]; // Background color override for current row. ImU32 BorderColorStrong; ImU32 BorderColorLight; float BorderX1; @@ -2018,6 +2027,7 @@ struct ImGuiTable ImS8 FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) ImS8 FreezeColumnsRequest; // Requested frozen columns count ImS8 FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) + ImS8 RowCellDataCount; // Number of RowCellData[] entries in current row bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index cb7a1b34..5e785af3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -307,13 +307,15 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->RawData.Size == 0) { // Allocate single buffer for our arrays - ImSpanAllocator<2> span_allocator; + ImSpanAllocator<3> span_allocator; span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn)); span_allocator.ReserveBytes(1, columns_count * sizeof(ImS8)); + span_allocator.ReserveBytes(2, columns_count * sizeof(ImGuiTableCellData)); table->RawData.resize(span_allocator.GetArenaSizeInBytes()); span_allocator.SetArenaBasePtr(table->RawData.Data); span_allocator.GetSpan(0, &table->Columns); span_allocator.GetSpan(1, &table->DisplayOrderToIndex); + span_allocator.GetSpan(2, &table->RowCellData); for (int n = 0; n < columns_count; n++) { @@ -1609,7 +1611,8 @@ void ImGui::TableBeginRow(ImGuiTable* table) // New row table->CurrentRow++; table->CurrentColumn = -1; - table->RowBgColor = IM_COL32_DISABLE; + table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; + table->RowCellDataCount = 0; table->IsInsideRow = true; // Begin frozen rows @@ -1626,7 +1629,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. if (table->RowFlags & ImGuiTableRowFlags_Headers) { - table->RowBgColor = GetColorU32(ImGuiCol_TableHeaderBg); + TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg)); if (table->CurrentRow == 0) table->IsUsingHeaders = true; } @@ -1655,14 +1658,18 @@ void ImGui::TableEndRow(ImGuiTable* table) if (table->CurrentRow == 0) table->LastFirstRowHeight = bg_y2 - bg_y1; - if (table->CurrentRow >= 0 && bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y) + const bool is_visible = table->CurrentRow >= 0 && bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y; + if (is_visible) { // Decide of background color for the row - ImU32 bg_col = 0; - if (table->RowBgColor != IM_COL32_DISABLE) - bg_col = table->RowBgColor; + ImU32 bg_col0 = 0; + ImU32 bg_col1 = 0; + if (table->RowBgColor[0] != IM_COL32_DISABLE) + bg_col0 = table->RowBgColor[0]; else if (table->Flags & ImGuiTableFlags_RowBg) - bg_col = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); + if (table->RowBgColor[1] != IM_COL32_DISABLE) + bg_col1 = table->RowBgColor[1]; // Decide of top border color ImU32 border_col = 0; @@ -1684,8 +1691,9 @@ void ImGui::TableEndRow(ImGuiTable* table) } } - const bool draw_stong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); - if (bg_col != 0 || border_col != 0 || draw_stong_bottom_border) + const bool draw_cell_bg_color = table->RowCellDataCount > 0; + const bool draw_strong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); + if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) { // In theory we could call SetWindowClipRectBeforeChannelChange() but since we know TableEndRow() is // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. @@ -1693,14 +1701,29 @@ void ImGui::TableEndRow(ImGuiTable* table) table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); } - // Draw background + // Draw row background // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle - if (bg_col) + if (bg_col0 || bg_col1) { - ImRect bg_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); - bg_rect.ClipWith(table->BackgroundClipRect); - if (bg_rect.Min.y < bg_rect.Max.y) - window->DrawList->AddRectFilledMultiColor(bg_rect.Min, bg_rect.Max, bg_col, bg_col, bg_col, bg_col); + ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); + row_rect.ClipWith(table->BackgroundClipRect); + if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0); + if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1); + } + + // Draw cell background color + if (draw_cell_bg_color) + { + ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCount - 1]; + for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) + { + ImGuiTableColumn* column = &table->Columns[cell_data->Column]; + ImRect cell_rect(column->MinX - table->CellSpacingX, bg_y1, column->MaxX, bg_y2); // FIXME-TABLE: Padding currently wrong until we finish the padding refactor + cell_rect.ClipWith(table->BackgroundClipRect); + window->DrawList->AddRectFilled(cell_rect.Min, cell_rect.Max, cell_data->BgColor); + } } // Draw top border @@ -1708,7 +1731,7 @@ void ImGui::TableEndRow(ImGuiTable* table) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size); // Draw bottom border at the row unfreezing mark (always strong) - if (draw_stong_bottom_border) + if (draw_strong_bottom_border) if (bg_y2 >= table->BackgroundClipRect.Min.y && bg_y2 < table->BackgroundClipRect.Max.y) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); } @@ -2305,6 +2328,46 @@ int ImGui::TableGetHoveredColumn() return (int)table->HoveredColumnBody; } +void ImGui::TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(bg_target != ImGuiTableBgTarget_None); + + if (color == IM_COL32_DISABLE) + color = 0; + + // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. + switch (bg_target) + { + case ImGuiTableBgTarget_CellBg: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + if (column_n == -1) + column_n = table->CurrentColumn; + if ((table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) == 0) + return; + ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCount++]; + cell_data->BgColor = color; + cell_data->Column = (ImS8)column_n; + break; + } + case ImGuiTableBgTarget_RowBg0: + case ImGuiTableBgTarget_RowBg1: + { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + IM_ASSERT(column_n == -1); + int bg_idx = (bg_target == ImGuiTableBgTarget_RowBg1) ? 1 : 0; + table->RowBgColor[bg_idx] = color; + break; + } + default: + IM_ASSERT(0); + } +} + void ImGui::TableSortSpecsSanitize(ImGuiTable* table) { IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); From eb18636e0287d90a3756115d90b99f4a7201e638 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 5 Aug 2020 19:26:54 +0200 Subject: [PATCH 076/144] Tables: Fix settings not being saved in child window (issue 3367) + fix for change in master. --- imgui_internal.h | 2 +- imgui_tables.cpp | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 513b7f24..73c7ba58 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2001,7 +2001,7 @@ struct ImGuiTable ImRect InnerClipRect; ImRect BackgroundClipRect; // We use this to cpu-clip cell background color fill ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. - ImRect HostWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() + ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() ImRect HostBackupClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() ImVec2 HostCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() ImGuiWindow* OuterWindow; // Parent window for the table diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 5e785af3..ad98797a 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -193,7 +193,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } flags = TableFixFlags(flags); - if (outer_window->Flags & ImGuiWindowFlags_NoSavedSettings) + + // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) +#ifdef IMGUI_HAS_DOCK + ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop; +#else + ImGuiWindow* window_for_settings = outer_window->RootWindow; +#endif + if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings) flags |= ImGuiTableFlags_NoSavedSettings; // Acquire storage for the table @@ -253,8 +260,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->HostIndentX = inner_window->DC.Indent.x; table->HostClipRect = inner_window->ClipRect; table->HostSkipItems = inner_window->SkipItems; - table->HostWorkRect = inner_window->WorkRect; + table->HostBackupParentWorkRect = inner_window->ParentWorkRect; table->HostCursorMaxPos = inner_window->DC.CursorMaxPos; + inner_window->ParentWorkRect = inner_window->WorkRect; // Borders // - None ........Content..... Pad .....Content........ @@ -1067,7 +1075,8 @@ void ImGui::EndTable() // Layout in outer window IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!"); PopID(); - inner_window->WorkRect = table->HostWorkRect; + inner_window->WorkRect = inner_window->ParentWorkRect; + inner_window->ParentWorkRect = table->HostBackupParentWorkRect; inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; outer_window->DC.ColumnsOffset.x = 0.0f; From 9372601322c620640e5a8c709d51a211bc663e54 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 17 Aug 2020 13:25:27 +0200 Subject: [PATCH 077/144] Tables: Fixed stacked popups incorrectly accessing g.CurrentTable of parent-in-stack windows. --- imgui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui.cpp b/imgui.cpp index bc1ac4b7..e12cbae1 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2931,6 +2931,7 @@ static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; g.CurrentWindow = window; + g.CurrentTable = window ? window->DC.CurrentTable : NULL; if (window) g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); } From 45a80716b19a3a8695e3fd9c0b1e0deaad97f1f7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 18 Aug 2020 18:39:57 +0200 Subject: [PATCH 078/144] Tables: Fixed three bugs + metrics tweaks. - Fixed bug when ending a table within another (outer table column offset was overwritten instead of restored). - Fixed assert when settings data has mismatching column count. - Fixed restoring g.CurrentTable when calling EndChild() from inside table inner window. - Made inactive tables grey in metrics. - Fix warning. (amended twice) --- imgui_internal.h | 3 ++- imgui_tables.cpp | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 73c7ba58..ed7b465d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -542,7 +542,7 @@ struct ImSpanAllocator int Offsets[CHUNKS]; ImSpanAllocator() { memset(this, 0, sizeof(*this)); } - inline void ReserveBytes(int n, size_t sz) { IM_ASSERT(n == CurrSpan && n < CHUNKS); Offsets[CurrSpan++] = TotalSize; TotalSize += (int)sz; } + inline void ReserveBytes(int n, size_t sz) { IM_ASSERT(n == CurrSpan && n < CHUNKS); IM_UNUSED(n); Offsets[CurrSpan++] = TotalSize; TotalSize += (int)sz; } inline int GetArenaSizeInBytes() { return TotalSize; } inline void SetArenaBasePtr(void* base_ptr) { BasePtr = (char*)base_ptr; } inline void* GetSpanPtrBegin(int n) { IM_ASSERT(n >= 0 && n < CHUNKS && CurrSpan == CHUNKS); return (void*)(BasePtr + Offsets[n]); } @@ -2004,6 +2004,7 @@ struct ImGuiTable ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() ImRect HostBackupClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() ImVec2 HostCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->ColumnsOffset at the end of BeginTable() ImGuiWindow* OuterWindow; // Parent window for the table ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window) ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names diff --git a/imgui_tables.cpp b/imgui_tables.cpp index ad98797a..1d66501a 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -261,6 +261,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->HostClipRect = inner_window->ClipRect; table->HostSkipItems = inner_window->SkipItems; table->HostBackupParentWorkRect = inner_window->ParentWorkRect; + table->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; table->HostCursorMaxPos = inner_window->DC.CursorMaxPos; inner_window->ParentWorkRect = inner_window->WorkRect; @@ -305,6 +306,8 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG g.CurrentTableStack.push_back(ImGuiPtrOrIndex(g.Tables.GetIndex(table))); g.CurrentTable = table; outer_window->DC.CurrentTable = table; + if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. + inner_window->DC.CurrentTable = table; if ((table_last_flags & ImGuiTableFlags_Reorderable) && !(flags & ImGuiTableFlags_Reorderable)) table->IsResetDisplayOrderRequest = true; @@ -1079,7 +1082,7 @@ void ImGui::EndTable() inner_window->ParentWorkRect = table->HostBackupParentWorkRect; inner_window->SkipItems = table->HostSkipItems; outer_window->DC.CursorPos = table->OuterRect.Min; - outer_window->DC.ColumnsOffset.x = 0.0f; + outer_window->DC.ColumnsOffset = table->HostBackupColumnsOffset; if (inner_window != outer_window) { EndChild(); @@ -1110,9 +1113,8 @@ void ImGui::EndTable() // Clear or restore current table, if any IM_ASSERT(g.CurrentWindow == outer_window); IM_ASSERT(g.CurrentTable == table); - outer_window->DC.CurrentTable = NULL; g.CurrentTableStack.pop_back(); - g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; + outer_window->DC.CurrentTable = g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; } // FIXME-TABLE: This is a mess, need to redesign how we render borders. @@ -2601,15 +2603,17 @@ void ImGui::TableLoadSettings(ImGuiTable* table) settings = TableSettingsFindByID(table->ID); if (settings == NULL) return; + if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return... + table->IsSettingsDirty = true; table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); } else { settings = TableGetBoundSettings(table); } + table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; - IM_ASSERT(settings->ColumnsCount == table->ColumnsCount); // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); @@ -2772,13 +2776,17 @@ void ImGui::DebugNodeTable(ImGuiTable* table) char buf[256]; char* p = buf; const char* buf_end = buf + IM_ARRAYSIZE(buf); - ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); + const bool is_active = (table->LastFrameActive >= ImGui::GetFrameCount() - 2); + ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*"); + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } bool open = TreeNode(table, "%s", buf); + if (!is_active) { PopStyleColor(); } if (IsItemHovered()) GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); if (!open) return; - BulletText("OuterWidth: %.1f, InnerWidth: %.1f%s", table->OuterRect.GetWidth(), table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); + BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f)", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight()); + BulletText("InnerWidth: %.1f%s", table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); BulletText("ColumnsWidth: %.1f, AutoFitWidth: %.1f", table->ColumnsTotalWidth, table->ColumnsAutoFitWidth); BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); From 8e97cdf8e8b41e92ec2ca6194335398797b4c2cf Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 24 Aug 2020 15:34:43 +0200 Subject: [PATCH 079/144] Tables: Fix for calling TableSetBgColor(ImGuiTableBgTarget_CellBg) multiple times on the same cell. --- imgui.h | 4 ++-- imgui_internal.h | 2 +- imgui_tables.cpp | 10 ++++++---- imgui_widgets.cpp | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/imgui.h b/imgui.h index ebbfa771..019e8721 100644 --- a/imgui.h +++ b/imgui.h @@ -1022,8 +1022,8 @@ enum ImGuiTabItemFlags_ // When ScrollX is on: // - Table defaults to ImGuiTableFlags_SizingPolicyFixedX -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed. // - Columns sizing policy allowed: Fixed/Auto mostly! Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable(). -// - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. -// - Stretch Columns, if any, will calculate their width using inner_width, assuming no scrolling (it really doesn't make sense to do otherwise). +// - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed. +// - Stretch Columns, if any, will calculate their width using inner_width, assuming no scrolling (it really doesn't make sense to do otherwise). // - Mixing up columns with different sizing policy is possible BUT can be tricky and has some side-effects and restrictions. // (their visible order and the scrolling state have subtle but necessary effects on how they can be manually resized). // The typical use of mixing sizing policies is to have ScrollX disabled, one or two Stretch Column and many Fixed Columns. diff --git a/imgui_internal.h b/imgui_internal.h index ed7b465d..3f733d62 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2028,7 +2028,7 @@ struct ImGuiTable ImS8 FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) ImS8 FreezeColumnsRequest; // Requested frozen columns count ImS8 FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) - ImS8 RowCellDataCount; // Number of RowCellData[] entries in current row + ImS8 RowCellDataCurrent; // Index of current RowCellData[] entry in current row bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 1d66501a..c780f1f2 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1623,7 +1623,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) table->CurrentRow++; table->CurrentColumn = -1; table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; - table->RowCellDataCount = 0; + table->RowCellDataCurrent = -1; table->IsInsideRow = true; // Begin frozen rows @@ -1702,7 +1702,7 @@ void ImGui::TableEndRow(ImGuiTable* table) } } - const bool draw_cell_bg_color = table->RowCellDataCount > 0; + const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; const bool draw_strong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) { @@ -1727,7 +1727,7 @@ void ImGui::TableEndRow(ImGuiTable* table) // Draw cell background color if (draw_cell_bg_color) { - ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCount - 1]; + ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCurrent]; for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) { ImGuiTableColumn* column = &table->Columns[cell_data->Column]; @@ -2359,7 +2359,9 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int colum column_n = table->CurrentColumn; if ((table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) == 0) return; - ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCount++]; + if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) + table->RowCellDataCurrent++; + ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCurrent]; cell_data->BgColor = color; cell_data->Column = (ImS8)column_n; break; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6954960b..3df58daa 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5999,7 +5999,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // which would be advantageous since most selectable are not selected. if (span_all_columns && window->DC.CurrentColumns) PushColumnsBackground(); - else if ((flags & ImGuiSelectableFlags_SpanAllColumns) && g.CurrentTable) + else if (span_all_columns && g.CurrentTable) PushTableBackground(); // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries From 3d573103b68b931cb3793d29d042c0c6e8d6a303 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2020 20:42:05 +0200 Subject: [PATCH 080/144] Tables: Fixed lower clipping when using ImGuiTableFlags_NoHostExtendY. --- imgui_tables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index c780f1f2..13fa9269 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -284,7 +284,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWith(table->HostClipRect); - table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? table->WorkRect.Max.y : inner_window->ClipRect.Max.y; + table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y; table->BackgroundClipRect = table->InnerClipRect; table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() From f6800e9d3b92513022716691364c0183ffa6b8bd Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 18 Sep 2020 20:44:50 +0200 Subject: [PATCH 081/144] Tables: Extend outer-most clip limits to match those of host when merging draw calls. Generally clarify/simplify ClipRect extending/merging code in TableReorderDrawChannelsForMerge(). Amend/fix Sep 23 --- imgui_tables.cpp | 69 +++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 13fa9269..a54a8f9e 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -69,18 +69,20 @@ // | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) // | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width // - TableSetupColumn() user submit columns details (optional) +// - TableUpdateLayout() [Internal] automatically called by the FIRST call to TableNextRow() or Table*Header(): lock all widths, columns positions, clipping rectangles +// | TableUpdateDrawChannels() - setup ImDrawList channels +// | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission +// | TableDrawContextMenu() - draw right-click context menu +//----------------------------------------------------------------------------- // - TableAutoHeaders() or TableHeader() user submit a headers row (optional) // | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction // | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) // - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() -// | TableUpdateLayout() - lock all widths, columns positions, clipping rectangles. called by the FIRST call to TableNextRow()! -// | - TableUpdateDrawChannels() - setup ImDrawList channels -// | - TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission -// | - TableDrawContextMenu() - draw right-click context menu // | TableEndCell() - close existing cell if not the first time // | TableBeginCell() - enter into current cell // - [...] user emit contents +//----------------------------------------------------------------------------- // - EndTable() user ends the table // | TableDrawBorders() - draw outer borders, inner vertical borders // | TableReorderDrawChannelsForMerge() - merge draw channels if clipping isn't required @@ -1375,7 +1377,6 @@ void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) int merge_group_mask = 0x00; MergeGroup merge_groups[4]; memset(merge_groups, 0, sizeof(merge_groups)); - bool merge_groups_all_fit_within_inner_rect = (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0; // 1. Scan channels and take note of those which can be merged for (int column_n = 0; column_n < table->ColumnsCount; column_n++) @@ -1396,7 +1397,8 @@ void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) if (src_channel->_CmdBuffer.Size != 1) continue; - // Find out the width of this merge group and check if it will fit in our column. + // Find out the width of this merge group and check if it will fit in our column + // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it) if (!(column->Flags & ImGuiTableColumnFlags_NoClipX)) { float width_contents; @@ -1410,28 +1412,15 @@ void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) continue; } - const int merge_group_dst_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); + const int merge_group_n = (is_frozen_h && column_n < table->FreezeColumnsCount ? 0 : 2) + (is_frozen_v ? merge_group_sub_n : 1); IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS); - MergeGroup* merge_group = &merge_groups[merge_group_dst_n]; + MergeGroup* merge_group = &merge_groups[merge_group_n]; if (merge_group->ChannelsCount == 0) merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); merge_group->ChannelsMask.SetBit(channel_no); merge_group->ChannelsCount++; merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); - merge_group_mask |= (1 << merge_group_dst_n); - - // If we end with a single group and hosted by the outer window, we'll attempt to merge our draw command - // with the existing outer window command. But we can only do so if our columns all fit within the expected - // clip rect, otherwise clipping will be incorrect when ScrollX is disabled. - // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column don't fit within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect - - // 2019/10/22: (1) This is breaking table_2_draw_calls but I cannot seem to repro what it is attempting to - // fix... cf git fce2e8dc "Fixed issue with clipping when outerwindow==innerwindow / support ScrollH without ScrollV." - // 2019/10/22: (2) Clamping code in TableUpdateLayout() seemingly made this not necessary... -#if 0 - if (column->MinX < table->InnerClipRect.Min.x || column->MaxX > table->InnerClipRect.Max.x) - merge_groups_all_fit_within_inner_rect = false; -#endif + merge_group_mask |= (1 << merge_group_n); } // Invalidate current draw channel @@ -1460,10 +1449,6 @@ void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) // 2. Rewrite channel list in our preferred order if (merge_group_mask != 0) { - // Conceptually we want to test if only 1 bit of merge_group_mask is set, but with no freezing we know it's always going to be group 3. - // We need to test for !is_frozen because any unfitting column will not be part of a merge group, so testing for merge_group_mask isn't enough. - const bool may_extend_clip_rect_to_host_rect = (merge_group_mask == (1 << 3)) && !is_frozen_v && !is_frozen_h; - // Use shared temporary storage so the allocation gets amortized g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - 1); ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data; @@ -1475,16 +1460,28 @@ void ImGui::TableReorderDrawChannelsForMerge(ImGuiTable* table) if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) { MergeGroup* merge_group = &merge_groups[merge_group_n]; - ImRect merge_clip_rect = merge_groups[merge_group_n].ClipRect; - if (may_extend_clip_rect_to_host_rect) - { - IM_ASSERT(merge_group_n == 3); - //GetOverlayDrawList()->AddRect(table->HostClipRect.Min, table->HostClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 3.0f); - //GetOverlayDrawList()->AddRect(table->InnerClipRect.Min, table->InnerClipRect.Max, IM_COL32(0, 255, 0, 200), 0.0f, ~0, 1.0f); - //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 2.0f); - merge_clip_rect.Add(merge_groups_all_fit_within_inner_rect ? table->HostClipRect : table->InnerClipRect); - //GetOverlayDrawList()->AddRect(merge_clip_rect.Min, merge_clip_rect.Max, IM_COL32(0, 255, 0, 200)); - } + ImRect merge_clip_rect = merge_group->ClipRect; + + // Extend outer-most clip limits to match those of host, so draw calls can be merged even if + // outer-most columns have some outer padding offsetting them from their parent ClipRect. + // The principal cases this is dealing with are: + // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge + // - Columns can use padding and have left-most ClipRect.Min.x and right-most ClipRect.Max.x != from host ClipRect -> will extend and match host ClipRect -> will merge + // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit + // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect. + if ((merge_group_n & 2) == 0 || !is_frozen_h) + merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, table->HostClipRect.Min.x); + if ((merge_group_n & 1) == 0 || !is_frozen_v) + merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, table->HostClipRect.Min.y); + if ((merge_group_n & 2) != 0) + merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, table->HostClipRect.Max.x); + if ((merge_group_n & 1) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) + merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, table->HostClipRect.Max.y); +#if 0 + GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, ~0, 1.0f); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); +#endif remaining_count -= merge_group->ChannelsCount; for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; From 931829f701143780d921b3478a8aef0df7b2d7bb Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 23 Sep 2020 12:53:10 +0200 Subject: [PATCH 082/144] Tables: (Breaking change) Sorting: Made it users responsability to clear SpecsDirty back to false, so TableGetSortSpecs() doesn't have side-effect any more. + comments --- imgui.h | 28 ++++++++++++++++------------ imgui_demo.cpp | 15 ++++++++++----- imgui_internal.h | 1 - imgui_tables.cpp | 9 +++------ 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/imgui.h b/imgui.h index 019e8721..42229755 100644 --- a/imgui.h +++ b/imgui.h @@ -688,17 +688,19 @@ namespace ImGui IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. // Tables: Headers & Columns declaration // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. - // - The name passed to TableSetupColumn() is used by TableAutoHeaders() and by the context-menu - // - Use TableAutoHeaders() to submit the whole header row, otherwise you may treat the header row as a regular row, manually call TableHeader() and other widgets. - // - Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) + // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableAutoHeaders() and context-menus. + // - Use TableAutoHeaders() to create a row and automatically submit a TableHeader() for each column. + // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) + // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu - IMGUI_API void TableHeader(const char* label); // submit one header cell manually. + IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) // Tables: Sorting // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. - // - You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since last call, or the first time. + // - When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time. + // Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! // - Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! - IMGUI_API const ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). // Tab Bars, Tabs IMGUI_API bool BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0); // create and append into a TabBar @@ -1890,15 +1892,17 @@ struct ImGuiTableSortSpecsColumn }; // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) -// Obtained by calling TableGetSortSpecs() +// Obtained by calling TableGetSortSpecs(). +// When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time. +// Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! struct ImGuiTableSortSpecs { - const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. - int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. - bool SpecsChanged; // Set to true by TableGetSortSpecs() call if the specs have changed since the previous call. Use this to sort again! - ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. + const ImGuiTableSortSpecsColumn* Specs; // Pointer to sort spec array. + int SpecsCount; // Sort spec count. Most often 1 unless e.g. ImGuiTableFlags_MultiSortable is enabled. + bool SpecsDirty; // Set to true when specs have changed since last time! Use this to sort again, then clear the flag. + ImU64 ColumnsMask; // Set to the mask of column indexes included in the Specs array. e.g. (1 << N) when column N is sorted. - ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsChanged = false; ColumnsMask = 0x00; } + ImGuiTableSortSpecs() { Specs = NULL; SpecsCount = 0; SpecsDirty = false; ColumnsMask = 0x00; } }; //----------------------------------------------------------------------------- diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 08005cbd..54f58586 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3250,6 +3250,8 @@ struct MyItem // however qsort doesn't allow passing user data to comparing function. // As a workaround, we are storing the sort specs in a static/global for the comparing function to access. // In your own use case you would probably pass the sort specs to your sorting/comparing functions directly and not use a global. + // We could technically call ImGui::TableGetSortSpecs() in CompareWithSortSpecs(), but considering that this function is called + // very often by the sorting algorithm it would be a little wasteful. static const ImGuiTableSortSpecs* s_current_sort_specs; // Compare function to be used by qsort() @@ -4201,12 +4203,14 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, -1.0f, MyItemColumnID_Quantity); // Sort our data if sort specs have been changed! - if (const ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) - if (sorts_specs->SpecsChanged && items.Size > 1) + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + if (sorts_specs->SpecsDirty) { MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. - qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); + if (items.Size > 1) + qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); MyItem::s_current_sort_specs = NULL; + sorts_specs->SpecsDirty = false; } // Display data @@ -4389,14 +4393,15 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Hidden", ImGuiTableColumnFlags_DefaultHide | ImGuiTableColumnFlags_NoSort); // Sort our data if sort specs have been changed! - const ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); - if (sorts_specs && sorts_specs->SpecsChanged) + ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); + if (sorts_specs && sorts_specs->SpecsDirty) items_need_sort = true; if (sorts_specs && items_need_sort && items.Size > 1) { MyItem::s_current_sort_specs = sorts_specs; // Store in variable accessible by the sort function. qsort(&items[0], (size_t)items.Size, sizeof(items[0]), MyItem::CompareWithSortSpecs); MyItem::s_current_sort_specs = NULL; + sorts_specs->SpecsDirty = false; } items_need_sort = false; diff --git a/imgui_internal.h b/imgui_internal.h index 3f733d62..261f1be1 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2033,7 +2033,6 @@ struct ImGuiTable bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; bool IsSortSpecsDirty; - bool IsSortSpecsChangedForUser; // Reported to end-user via TableGetSortSpecs()->SpecsChanged and then clear. bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). bool IsSettingsRequestLoad; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a54a8f9e..efa41dd9 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2298,7 +2298,7 @@ void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* click // You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since // last call, or the first time. // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! -const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() +ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; @@ -2310,8 +2310,6 @@ const ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() if (table->IsSortSpecsDirty) TableSortSpecsBuild(table); - table->SortSpecs.SpecsChanged = table->IsSortSpecsChangedForUser; - table->IsSortSpecsChangedForUser = false; return table->SortSpecs.SpecsCount ? &table->SortSpecs : NULL; } @@ -2466,9 +2464,8 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) } table->SortSpecs.Specs = table->SortSpecsData.Data; table->SortSpecs.SpecsCount = table->SortSpecsData.Size; - - table->IsSortSpecsDirty = false; - table->IsSortSpecsChangedForUser = true; + table->SortSpecs.SpecsDirty = true; // Mark as dirty for user + table->IsSortSpecsDirty = false; // Mark as not dirty for us } //------------------------------------------------------------------------- From 8ec05fc0342e81a023fba85b8b19e7ad6104ff46 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 23 Sep 2020 14:22:41 +0200 Subject: [PATCH 083/144] Tables: Fixed holding on table pointers accross resize/invalidation of the pool buffer. --- imgui.cpp | 3 ++- imgui_internal.h | 2 +- imgui_tables.cpp | 10 ++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e12cbae1..823fe725 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2931,7 +2931,7 @@ static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; g.CurrentWindow = window; - g.CurrentTable = window ? window->DC.CurrentTable : NULL; + g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; if (window) g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); } @@ -5623,6 +5623,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX); window->IDStack.resize(1); window->DrawList->_ResetForNewFrame(); + window->DC.CurrentTableIdx = -1; // Restore buffer capacity when woken from a compacted state, to avoid if (window->MemoryCompacted) diff --git a/imgui_internal.h b/imgui_internal.h index 261f1be1..054252f2 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1657,7 +1657,7 @@ struct IMGUI_API ImGuiWindowTempData ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiOldColumns* CurrentColumns; // Current columns set - ImGuiTable* CurrentTable; // Current table set + int CurrentTableIdx; // Current table index (into g.Tables) ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() int FocusCounterRegular; // (Legacy Focus/Tabbing system) Sequential counter, start at -1 and increase as assigned via FocusableItemRegister() (FIXME-NAV: Needs redesign) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index efa41dd9..51249f4b 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -305,11 +305,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); // Make table current - g.CurrentTableStack.push_back(ImGuiPtrOrIndex(g.Tables.GetIndex(table))); + const int table_idx = g.Tables.GetIndex(table); + g.CurrentTableStack.push_back(ImGuiPtrOrIndex(table_idx)); g.CurrentTable = table; - outer_window->DC.CurrentTable = table; + outer_window->DC.CurrentTableIdx = table_idx; if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. - inner_window->DC.CurrentTable = table; + inner_window->DC.CurrentTableIdx = table_idx; if ((table_last_flags & ImGuiTableFlags_Reorderable) && !(flags & ImGuiTableFlags_Reorderable)) table->IsResetDisplayOrderRequest = true; @@ -1116,7 +1117,8 @@ void ImGui::EndTable() IM_ASSERT(g.CurrentWindow == outer_window); IM_ASSERT(g.CurrentTable == table); g.CurrentTableStack.pop_back(); - outer_window->DC.CurrentTable = g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; + g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL; + outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; } // FIXME-TABLE: This is a mess, need to redesign how we render borders. From 36b2f3b4f1bb245ec59dc06e4c1b78a791d66c1c Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 23 Sep 2020 16:42:44 +0200 Subject: [PATCH 084/144] Tables: renamed ImGuiTableFlags_NoClipX to ImGuiTableFlags_NoClip, clarified purpose, moved lower in the list as it doesn't need to be so prominent. --- imgui.h | 12 ++++++------ imgui_demo.cpp | 7 ++++--- imgui_tables.cpp | 18 +++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/imgui.h b/imgui.h index 42229755..83b5747d 100644 --- a/imgui.h +++ b/imgui.h @@ -1052,12 +1052,12 @@ enum ImGuiTableFlags_ ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. ImGuiTableFlags_BordersFullHeightV = 1 << 11, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. // Padding, Sizing - ImGuiTableFlags_NoClipX = 1 << 14, // Disable pushing clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow) - ImGuiTableFlags_SizingPolicyFixedX = 1 << 15, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. - ImGuiTableFlags_SizingPolicyStretchX = 1 << 16, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. - ImGuiTableFlags_NoHeadersWidth = 1 << 17, // Disable header width contribution to automatic width calculation. - ImGuiTableFlags_NoHostExtendY = 1 << 18, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) - ImGuiTableFlags_NoKeepColumnsVisible = 1 << 19, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. + ImGuiTableFlags_SizingPolicyFixedX = 1 << 12, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 13, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. + ImGuiTableFlags_NoHeadersWidth = 1 << 14, // Disable header width contribution to automatic width calculation. + ImGuiTableFlags_NoHostExtendY = 1 << 15, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 16, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. + ImGuiTableFlags_NoClip = 1 << 17, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options. // Scrolling ImGuiTableFlags_ScrollX = 1 << 20, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. ImGuiTableFlags_ScrollY = 1 << 21, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 54f58586..f26d6349 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3828,7 +3828,7 @@ static void ShowDemoWindowTables() flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyFixedX); // Can't specify both sizing polices so we clear the other ImGui::SameLine(); HelpMarker("Default if _ScrollX if enabled. Makes columns use _WidthFixed by default, or _WidthAlwaysAutoResize if _Resizable is not set."); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); - ImGui::CheckboxFlags("ImGuiTableFlags_NoClipX", (unsigned int*)&flags, ImGuiTableFlags_NoClipX); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", (unsigned int*)&flags, ImGuiTableFlags_NoClip); if (ImGui::BeginTable("##3ways", 3, flags, ImVec2(0, 100))) { @@ -4296,7 +4296,6 @@ static void ShowDemoWindowTables() ImGui::BulletText("Padding, Sizing:"); ImGui::Indent(); - ImGui::CheckboxFlags("ImGuiTableFlags_NoClipX", (unsigned int*)&flags, ImGuiTableFlags_NoClipX); if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyStretchX); // Can't specify both sizing polices so we clear the other ImGui::SameLine(); HelpMarker("[Default if ScrollX is off]\nFit all columns within available width (or specified inner_width). Fixed and Stretch columns allowed."); @@ -4307,6 +4306,8 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", (unsigned int*)&flags, ImGuiTableFlags_NoHostExtendY); ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", (unsigned int*)&flags, ImGuiTableFlags_NoKeepColumnsVisible); ImGui::SameLine(); HelpMarker("Only available if ScrollX is disabled."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", (unsigned int*)&flags, ImGuiTableFlags_NoClip); + ImGui::SameLine(); HelpMarker("Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options."); ImGui::Unindent(); ImGui::BulletText("Scrolling:"); @@ -4424,7 +4425,7 @@ static void ShowDemoWindowTables() for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) #else { - for (int row_n = 0; row_n < items_count; n++) + for (int row_n = 0; row_n < items_count; row_n++) #endif { MyItem* item = &items[row_n]; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 51249f4b..65701724 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -124,10 +124,10 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) flags &= ~ImGuiTableFlags_NoHostExtendY; - // Adjust flags: we don't support NoClipX with (FreezeColumns > 0) + // Adjust flags: we don't support NoClip with (FreezeColumns > 0) // We could with some work but it doesn't appear to be worth the effort. - if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) - flags &= ~ImGuiTableFlags_NoClipX; + //if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) + // flags &= ~ImGuiTableFlags_NoClip; return flags; } @@ -503,7 +503,7 @@ void ImGui::TableUpdateDrawChannels(ImGuiTable* table) // - FreezeRows || FreezeColumns --> 1+N*2 (unless scrolling value is zero) // - FreezeRows && FreezeColunns --> 2+N*2 (unless scrolling value is zero) const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; - const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClipX) ? 1 : table->ColumnsVisibleCount; + const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsVisibleCount; const int channels_for_background = 1; const int channels_for_dummy = (table->ColumnsVisibleCount < table->ColumnsCount || table->VisibleUnclippedMaskByIndex != table->VisibleMaskByIndex) ? +1 : 0; const int channels_total = channels_for_background + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; @@ -518,7 +518,7 @@ void ImGui::TableUpdateDrawChannels(ImGuiTable* table) { column->DrawChannelRowsBeforeFreeze = (ImS8)(draw_channel_current); column->DrawChannelRowsAfterFreeze = (ImS8)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row : 0)); - if (!(table->Flags & ImGuiTableFlags_NoClipX)) + if (!(table->Flags & ImGuiTableFlags_NoClip)) draw_channel_current++; } else @@ -910,7 +910,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Initial state ImGuiWindow* inner_window = table->InnerWindow; - if (table->Flags & ImGuiTableFlags_NoClipX) + if (table->Flags & ImGuiTableFlags_NoClip) table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 1); else inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); @@ -1017,7 +1017,7 @@ void ImGui::EndTable() table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); table->LastOuterHeight = table->OuterRect.GetHeight(); - if (!(flags & ImGuiTableFlags_NoClipX)) + if (!(flags & ImGuiTableFlags_NoClip)) inner_window->DrawList->PopClipRect(); inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); @@ -1050,7 +1050,7 @@ void ImGui::EndTable() // Flatten channels and merge draw calls table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0); - if ((table->Flags & ImGuiTableFlags_NoClipX) == 0) + if ((table->Flags & ImGuiTableFlags_NoClip) == 0) TableReorderDrawChannelsForMerge(table); table->DrawSplitter.Merge(inner_window->DrawList); @@ -1808,7 +1808,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); window->SkipItems = column->SkipItems; - if (table->Flags & ImGuiTableFlags_NoClipX) + if (table->Flags & ImGuiTableFlags_NoClip) { table->DrawSplitter.SetCurrentChannel(window->DrawList, 1); } From 30216083920fa2bcefa5760d0a2e07b8cb52bb46 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 23 Sep 2020 17:53:52 +0200 Subject: [PATCH 085/144] Tables: (Breaking) Add TableSetupScrollFreeze() api, remove ImGuiTableFlags_ScrollFreezeXXX flags, tweak comments, move columns block. Avoid awkwardly named ScrollFreeze flags, raise limit over 3, and will allow for future api maybe freezing bottom/right side. --- imgui.h | 98 ++++++++++++++++++++++++------------------------ imgui_demo.cpp | 40 +++++++++----------- imgui_tables.cpp | 39 ++++++++++--------- 3 files changed, 87 insertions(+), 90 deletions(-) diff --git a/imgui.h b/imgui.h index 83b5747d..b9515138 100644 --- a/imgui.h +++ b/imgui.h @@ -652,11 +652,55 @@ namespace ImGui // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel: return true if any popup is open. IMGUI_API bool IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0); // return true if the popup is open. - // Columns + // Tables + // [ALPHA API] API will evolve! (see: FIXME-TABLE) + // - Full-featured replacement for old Columns API + // - See Demo->Tables for details. + // - See ImGuiTableFlags_ and ImGuiTableColumnsFlags_ enums for a description of available flags. + // The typical call flow is: + // - 1. Call BeginTable() + // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults + // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows + // - 4. Optionally call TableAutoHeaders() to submit a header row (names will be pulled from data submitted to TableSetupColumns) + // - 4. Populate contents + // - In most situations you can use TableNextRow() + TableSetColumnIndex() to start appending into a column. + // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, + // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). + // - Submit your content with regular ImGui function. + // - 5. Call EndTable() + #define IMGUI_HAS_TABLE 1 + IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); + IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! + IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. + IMGUI_API bool TableNextCell(); // append into the next column (next column, or next row if currently in last column). Return true if column is visible. + IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. + IMGUI_API int TableGetColumnIndex(); // return current column index. + // Tables: Headers & Columns declaration + // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. + // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. + // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableAutoHeaders() and context-menus. + // - Use TableAutoHeaders() to create a row and automatically submit a TableHeader() for each column. + // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) + // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). + IMGUI_API void TableSetupScrollFreeze(int columns, int rows); + IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); + IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu + IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) + // Tables: Miscellaneous functions + // - Most functions taking 'int column_n' treat the default value of -1 as the same as passing the current column index + // - Sorting: call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. + // When 'SpecsDirty == true' you should sort your data. It will be true when sorting specs have changed since last call, or the first time. + // Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! + // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). + IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. + IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. + IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). + IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. + + // Columns (Legacy API, prefer using Tables) // - You can also use SameLine(pos_x) to mimic simplified columns. - // - The columns API is work-in-progress and rather lacking (columns are arguably the worst part of dear imgui at the moment!) - // - There is a maximum of 64 columns. - // - Currently working on new 'Tables' api which will replace columns around Q2 2020 (see GitHub #2957). IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished IMGUI_API int GetColumnIndex(); // get current column index @@ -666,42 +710,6 @@ namespace ImGui IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column IMGUI_API int GetColumnsCount(); - // Tables - // [ALPHA API] API will evolve! (FIXME-TABLE) - // - Full-featured replacement for old Columns API - // - In most situations you can use TableNextRow() + TableSetColumnIndex() to populate a table. - // - If you are using tables as a sort of grid, populating every columns with the same type of contents, - // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). - // - See Demo->Tables for details. - // - See ImGuiTableFlags_ and ImGuiTableColumnsFlags_ enums for a description of available flags. - #define IMGUI_HAS_TABLE 1 - IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); - IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! - IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. - IMGUI_API bool TableNextCell(); // append into the next column (next column, or next row if currently in last column). Return true if column is visible. - IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. - IMGUI_API int TableGetColumnIndex(); // return current column index. - IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. - IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. - IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. - IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. - IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. - // Tables: Headers & Columns declaration - // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. - // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableAutoHeaders() and context-menus. - // - Use TableAutoHeaders() to create a row and automatically submit a TableHeader() for each column. - // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) - // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). - IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); - IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu - IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) - // Tables: Sorting - // - Call TableGetSortSpecs() to retrieve latest sort specs for the table. Return value will be NULL if no sorting. - // - When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time. - // Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! - // - Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! - IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). - // 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! @@ -1062,19 +1070,9 @@ enum ImGuiTableFlags_ ImGuiTableFlags_ScrollX = 1 << 20, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. ImGuiTableFlags_ScrollY = 1 << 21, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, - ImGuiTableFlags_ScrollFreezeTopRow = 1 << 22, // We can lock 1 to 3 rows (starting from the top). Use with ScrollY enabled. - ImGuiTableFlags_ScrollFreeze2Rows = 2 << 22, - ImGuiTableFlags_ScrollFreeze3Rows = 3 << 22, - ImGuiTableFlags_ScrollFreezeLeftColumn = 1 << 24, // We can lock 1 to 3 columns (starting from the left). Use with ScrollX enabled. - ImGuiTableFlags_ScrollFreeze2Columns = 2 << 24, - ImGuiTableFlags_ScrollFreeze3Columns = 3 << 24, // [Internal] Combinations and masks ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX, - ImGuiTableFlags_ScrollFreezeRowsShift_ = 22, - ImGuiTableFlags_ScrollFreezeColumnsShift_ = 24, - ImGuiTableFlags_ScrollFreezeRowsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeRowsShift_, - ImGuiTableFlags_ScrollFreezeColumnsMask_ = 0x03 << ImGuiTableFlags_ScrollFreezeColumnsShift_ }; // Flags for ImGui::TableSetupColumn() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f26d6349..78c4ecb0 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3630,12 +3630,12 @@ static void ShowDemoWindowTables() { HelpMarker("Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\nWe also demonstrate using ImGuiListClipper to virtualize the submission of many items."); ImVec2 size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 7); - static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); - ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); if (ImGui::BeginTable("##table1", 3, flags, size)) { + ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); @@ -3665,14 +3665,19 @@ static void ShowDemoWindowTables() { HelpMarker("When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingPolicyFixedX, as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\nAlso note that as of the current version, you will almost always want to enable ScrollY along with ScrollX, because the container window won't automatically extend vertically to fix contents (this may be improved in future versions)."); ImVec2 size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 10); - static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_ScrollFreezeLeftColumn | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + static int freeze_cols = 1; + static int freeze_rows = 1; ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); - ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); - ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); if (ImGui::BeginTable("##table1", 7, flags, size)) { - ImGui::TableSetupColumn("Line #", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of ImGuiTableFlags_ScrollFreezeLeftColumn + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + ImGui::TableSetupColumn("Line #", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of TableSetupScrollFreeze() ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); @@ -3978,7 +3983,6 @@ static void ShowDemoWindowTables() { static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg; //ImGui::CheckboxFlags("ImGuiTableFlags_Scroll", (unsigned int*)&flags, ImGuiTableFlags_Scroll); - //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); if (ImGui::BeginTable("##3ways", 3, flags)) { @@ -4187,7 +4191,7 @@ static void ShowDemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV - | ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollFreezeTopRow; + | ImGuiTableFlags_ScrollY; if (ImGui::BeginTable("##table", 4, flags, ImVec2(0, 250), 0.0f)) { // Declare columns @@ -4201,6 +4205,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Name); ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, -1.0f, MyItemColumnID_Quantity); + ImGui::TableSetupScrollFreeze(0, 1); // Make row always visible // Sort our data if sort specs have been changed! if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) @@ -4246,14 +4251,14 @@ static void ShowDemoWindowTables() ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY - | ImGuiTableFlags_ScrollFreezeTopRow | ImGuiTableFlags_ScrollFreezeLeftColumn | ImGuiTableFlags_SizingPolicyFixedX ; enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_FillButton, CT_Selectable }; static int contents_type = CT_FillButton; const char* contents_type_names[] = { "Text", "Button", "SmallButton", "FillButton", "Selectable" }; - + static int freeze_cols = 1; + static int freeze_rows = 1; static int items_count = IM_ARRAYSIZE(template_items_names); static ImVec2 outer_size_value = ImVec2(0, 250); static float row_min_height = 0.0f; // Auto @@ -4314,20 +4319,10 @@ static void ShowDemoWindowTables() ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); - - // For the purpose of our "advanced" demo, we expose the 3 freezing variants on both axises instead of only exposing the most common flag. - //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeTopRow", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeTopRow); - //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollFreezeLeftColumn", (unsigned int*)&flags, ImGuiTableFlags_ScrollFreezeLeftColumn); - int freeze_row_count = (flags & ImGuiTableFlags_ScrollFreezeRowsMask_) >> ImGuiTableFlags_ScrollFreezeRowsShift_; - int freeze_col_count = (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) >> ImGuiTableFlags_ScrollFreezeColumnsShift_; ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); - if (ImGui::DragInt("ImGuiTableFlags_ScrollFreezeTopRow/2Rows/3Rows", &freeze_row_count, 0.2f, 0, 3)) - if (freeze_row_count >= 0 && freeze_row_count <= 3) - flags = (flags & ~ImGuiTableFlags_ScrollFreezeRowsMask_) | (freeze_row_count << ImGuiTableFlags_ScrollFreezeRowsShift_); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); - if (ImGui::DragInt("ImGuiTableFlags_ScrollFreezeLeftColumn/2Columns/3Columns", &freeze_col_count, 0.2f, 0, 3)) - if (freeze_col_count >= 0 && freeze_col_count <= 3) - flags = (flags & ~ImGuiTableFlags_ScrollFreezeColumnsMask_) | (freeze_col_count << ImGuiTableFlags_ScrollFreezeColumnsShift_); + ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); ImGui::Unindent(); @@ -4386,6 +4381,7 @@ static void ShowDemoWindowTables() // Declare columns // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | (lock_first_column_visibility ? ImGuiTableColumnFlags_NoHide : 0), -1.0f, MyItemColumnID_ID); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Name); ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 65701724..b67f1107 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -69,6 +69,7 @@ // | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) // | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width // - TableSetupColumn() user submit columns details (optional) +// - TableSetupScrollFreeze() user submit scroll freeze information (optional) // - TableUpdateLayout() [Internal] automatically called by the FIRST call to TableNextRow() or Table*Header(): lock all widths, columns positions, clipping rectangles // | TableUpdateDrawChannels() - setup ImDrawList channels // | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission @@ -113,22 +114,10 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) if (flags & ImGuiTableFlags_Resizable) flags |= ImGuiTableFlags_BordersInnerV; - // Adjust flags: disable top rows freezing if there's no scrolling. - // We could want to assert if ScrollFreeze was set without the corresponding scroll flag, but that would hinder demos. - if ((flags & ImGuiTableFlags_ScrollX) == 0) - flags &= ~ImGuiTableFlags_ScrollFreezeColumnsMask_; - if ((flags & ImGuiTableFlags_ScrollY) == 0) - flags &= ~ImGuiTableFlags_ScrollFreezeRowsMask_; - // Adjust flags: disable NoHostExtendY if we have any scrolling going on if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) flags &= ~ImGuiTableFlags_NoHostExtendY; - // Adjust flags: we don't support NoClip with (FreezeColumns > 0) - // We could with some work but it doesn't appear to be worth the effort. - //if (flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) - // flags &= ~ImGuiTableFlags_NoClip; - return flags; } @@ -290,11 +279,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->BackgroundClipRect = table->InnerClipRect; table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() - table->FreezeRowsRequest = (ImS8)((flags & ImGuiTableFlags_ScrollFreezeRowsMask_) >> ImGuiTableFlags_ScrollFreezeRowsShift_); - table->FreezeRowsCount = (inner_window->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; - table->FreezeColumnsRequest = (ImS8)((flags & ImGuiTableFlags_ScrollFreezeColumnsMask_) >> ImGuiTableFlags_ScrollFreezeColumnsShift_); - table->FreezeColumnsCount = (inner_window->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; - table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); + table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any + table->FreezeColumnsRequest = table->FreezeColumnsCount = 0; + table->IsFreezeRowsPassed = true; table->DeclColumnsCount = 0; table->RightMostVisibleColumn = -1; @@ -487,6 +474,22 @@ void ImGui::TableBeginUpdateColumns(ImGuiTable* table) table->InnerWindow->SkipItems = false; } +void ImGui::TableSetupScrollFreeze(int columns, int rows) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); + IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit + + table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImS8)columns : 0; + table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; + table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImS8)rows : 0; + table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; + table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); +} + void ImGui::TableUpdateDrawChannels(ImGuiTable* table) { // Allocate draw channels. @@ -1527,7 +1530,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); - IM_ASSERT(!table->IsLayoutLocked && "Need to call call TableSetupColumn() before first row!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); IM_ASSERT(table->DeclColumnsCount >= 0 && table->DeclColumnsCount < table->ColumnsCount && "Called TableSetupColumn() too many times!"); ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; From 9b37087fbee0d8b9a33bb4ec456a5a349ba1a18b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 24 Sep 2020 14:33:40 +0200 Subject: [PATCH 086/144] Tables: (Breaking) Rename TableAutoHeaders() to TableHeadersRow() + added TableGetColumnCount(). --- imgui.h | 11 ++++++----- imgui_demo.cpp | 36 ++++++++++++++++++------------------ imgui_tables.cpp | 37 ++++++++++++++++++++----------------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/imgui.h b/imgui.h index b9515138..9f0e9c8b 100644 --- a/imgui.h +++ b/imgui.h @@ -661,7 +661,7 @@ namespace ImGui // - 1. Call BeginTable() // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows - // - 4. Optionally call TableAutoHeaders() to submit a header row (names will be pulled from data submitted to TableSetupColumns) + // - 4. Optionally call TableHeadersRow() to submit a header row (names will be pulled from data submitted to TableSetupColumns) // - 4. Populate contents // - In most situations you can use TableNextRow() + TableSetColumnIndex() to start appending into a column. // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, @@ -678,13 +678,13 @@ namespace ImGui // Tables: Headers & Columns declaration // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. - // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableAutoHeaders() and context-menus. - // - Use TableAutoHeaders() to create a row and automatically submit a TableHeader() for each column. + // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableHeadersRow() and context-menus. + // - Use TableHeadersRow() to create a row and automatically submit a TableHeader() for each column. // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). IMGUI_API void TableSetupScrollFreeze(int columns, int rows); IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); - IMGUI_API void TableAutoHeaders(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu + IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) // Tables: Miscellaneous functions // - Most functions taking 'int column_n' treat the default value of -1 as the same as passing the current column index @@ -692,6 +692,7 @@ namespace ImGui // When 'SpecsDirty == true' you should sort your data. It will be true when sorting specs have changed since last call, or the first time. // Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). + IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. @@ -1042,7 +1043,7 @@ enum ImGuiTableFlags_ // Features ImGuiTableFlags_None = 0, ImGuiTableFlags_Resizable = 1 << 0, // Allow resizing columns. - ImGuiTableFlags_Reorderable = 1 << 1, // Allow reordering columns (need calling TableSetupColumn() + TableAutoHeaders() or TableHeaders() to display headers) + ImGuiTableFlags_Reorderable = 1 << 1, // Allow reordering columns (need calling TableSetupColumn() + TableHeadersRow() to display headers) ImGuiTableFlags_Hideable = 1 << 2, // Allow hiding columns (with right-click on header) (FIXME-TABLE: allow without headers). ImGuiTableFlags_Sortable = 1 << 3, // Allow sorting on one column (sort_specs_count will always be == 1). Call TableGetSortSpecs() to obtain sort specs. ImGuiTableFlags_MultiSortable = 1 << 4, // Allow sorting on multiple columns by holding Shift (sort_specs_count may be > 1). Call TableGetSortSpecs() to obtain sort specs. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 78c4ecb0..f95bbffe 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3513,7 +3513,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed);// | ImGuiTableColumnFlags_NoResize); ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -3533,7 +3533,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("DDD", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("EEE", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("FFF", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultHide); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -3560,12 +3560,12 @@ static void ShowDemoWindowTables() if (ImGui::BeginTable("##table1", 3, flags)) { - // Submit columns name with TableSetupColumn() and call TableAutoHeaders() to create a row with a header in each column. + // Submit columns name with TableSetupColumn() and call TableHeadersRow() to create a row with a header in each column. // (Later we will show how TableSetupColumn() has other uses, optional flags, sizing weight etc.) ImGui::TableSetupColumn("One"); ImGui::TableSetupColumn("Two"); ImGui::TableSetupColumn("Three"); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 6; row++) { ImGui::TableNextRow(); @@ -3583,7 +3583,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("One"); ImGui::TableSetupColumn("Two"); ImGui::TableSetupColumn("Three"); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 6; row++) { ImGui::TableNextRow(); @@ -3639,7 +3639,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); ImGuiListClipper clipper; clipper.Begin(1000); while (clipper.Step()) @@ -3684,7 +3684,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Four", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Five", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Six", ImGuiTableColumnFlags_None); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 20; row++) { ImGui::TableNextRow(); @@ -3750,7 +3750,7 @@ static void ShowDemoWindowTables() { for (int column = 0; column < column_count; column++) ImGui::TableSetupColumn(column_names[column], column_flags[column]); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 8; row++) { ImGui::Indent(2.0f); // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags. @@ -3778,7 +3778,7 @@ static void ShowDemoWindowTables() { ImGui::TableSetupColumn("A0"); ImGui::TableSetupColumn("A1"); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::Text("A0 Cell 0"); { @@ -3787,7 +3787,7 @@ static void ShowDemoWindowTables() { ImGui::TableSetupColumn("B0"); ImGui::TableSetupColumn("B1"); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); ImGui::Text("B0 Cell 0"); @@ -3990,7 +3990,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 6); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 10); - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); // Simple storage to output a dummy file-system. struct MyTreeNode @@ -4048,7 +4048,7 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } - // Demonstrate using TableHeader() calls instead of TableAutoHeaders() + // Demonstrate using TableHeader() calls instead of TableHeadersRow() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Custom headers")) @@ -4064,7 +4064,7 @@ static void ShowDemoWindowTables() // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox. static bool column_selected[3] = {}; - // Instead of calling TableAutoHeaders() we'll submit custom headers ourselves + // Instead of calling TableHeadersRow() we'll submit custom headers ourselves ImGui::TableNextRow(ImGuiTableRowFlags_Headers); for (int column = 0; column < COLUMNS_COUNT; column++) { @@ -4095,12 +4095,12 @@ static void ShowDemoWindowTables() ImGui::TreePop(); } - // Demonstrate creating custom context menus inside columns, while playing it nice with context menus provided by TableHeader()/TableAutoHeaders() + // Demonstrate creating custom context menus inside columns, while playing it nice with context menus provided by TableHeadersRow()/TableHeader() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Context menus")) { - HelpMarker("By default, TableHeader()/TableAutoHeaders() will open a context-menu on right-click."); + HelpMarker("By default, TableHeadersRow()/TableHeader() will open a context-menu on right-click."); ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders; const int COLUMNS_COUNT = 3; if (ImGui::BeginTable("##table1", COLUMNS_COUNT, flags)) @@ -4110,7 +4110,7 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("Three"); // Context Menu 1: right-click on header (including empty section after the third column!) should open Default Table Popup - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); for (int row = 0; row < 4; row++) { ImGui::TableNextRow(); @@ -4219,7 +4219,7 @@ static void ShowDemoWindowTables() } // Display data - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); ImGuiListClipper clipper; clipper.Begin(items.Size); while (clipper.Step()) @@ -4408,7 +4408,7 @@ static void ShowDemoWindowTables() // Show headers if (show_headers) - ImGui::TableAutoHeaders(); + ImGui::TableHeadersRow(); // Show data // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b67f1107..9a28a00d 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -75,11 +75,11 @@ // | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission // | TableDrawContextMenu() - draw right-click context menu //----------------------------------------------------------------------------- -// - TableAutoHeaders() or TableHeader() user submit a headers row (optional) +// - TableHeadersRow() or TableHeader() user submit a headers row (optional) // | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction // | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) -// - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableAutoHeaders() +// - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableHeadersRow() // | TableEndCell() - close existing cell if not the first time // | TableBeginCell() - enter into current cell // - [...] user emit contents @@ -1867,6 +1867,13 @@ bool ImGui::TableNextCell() return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } +int ImGui::TableGetColumnCount() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + return table ? table->ColumnsCount : 0; +} + const char* ImGui::TableGetColumnName(int column_n) { ImGuiContext& g = *GImGui; @@ -2066,18 +2073,20 @@ void ImGui::TableOpenContextMenu(ImGuiTable* table, int column_n) // This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). // The intent is that advanced users willing to create customized headers would not need to use this helper // and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets. -void ImGui::TableAutoHeaders() +// See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this. +void ImGui::TableHeadersRow() { ImGuiStyle& style = ImGui::GetStyle(); ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); - const int columns_count = table->ColumnsCount; + IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); // Calculate row height (for the unlikely case that labels may be are multi-line) - float row_y1 = GetCursorScreenPos().y; + // If we didn't do that, uneven header height would work but their highlight won't cover the full row height. float row_height = GetTextLineHeight(); + const float row_y1 = GetCursorScreenPos().y; + const int columns_count = TableGetColumnCount(); for (int column_n = 0; column_n < columns_count; column_n++) if (TableGetColumnIsVisible(column_n)) row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); @@ -2095,8 +2104,6 @@ void ImGui::TableAutoHeaders() if (!TableSetColumnIndex(column_n)) continue; - const char* name = TableGetColumnName(column_n); - // [DEBUG] Test custom user elements #if 0 if (column_n < 2) @@ -2112,23 +2119,19 @@ void ImGui::TableAutoHeaders() #endif // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them) - // - in your own code you may omit the PushID/PopID all-together, provided you know you know they won't collide + // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier. + const char* name = TableGetColumnName(column_n); PushID(table->InstanceCurrent * table->ColumnsCount + column_n); TableHeader(name); PopID(); } - // FIXME-TABLE: This is not user-land code any more + need to explain WHY this is here! (added in fa88f023) - //window->SkipItems = table->HostSkipItems; - // Allow opening popup from the right-most section after the last column. - // (We don't actually need to use ImGuiHoveredFlags_AllowWhenBlockedByPopup because in reality this is generally - // not required anymore.. because popup opening code tends to be reacting on IsMouseReleased() and the click would - // already have closed any other popups!) // FIXME-TABLE: TableOpenContextMenu() is not public yet. + ImVec2 mouse_pos = ImGui::GetMousePos(); if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) - if (g.IO.MousePos.y >= row_y1 && g.IO.MousePos.y < row_y1 + row_height) + if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height) TableOpenContextMenu(table, -1); // Will open a non-column-specific popup. } @@ -2145,7 +2148,7 @@ void ImGui::TableHeader(const char* label) return; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableAutoHeaders() after BeginTable()!"); + IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; From cc12ea084bb68893fc23e220ce44bcf3a6764acd Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 24 Sep 2020 15:10:47 +0200 Subject: [PATCH 087/144] Tables: Added TableSetColumnSortDirection() + added in default context menu code (disabled, feels unnecessary, but work is done to ensure programmatic access) --- imgui.h | 2 +- imgui_internal.h | 4 +- imgui_tables.cpp | 104 +++++++++++++++++++++++++++-------------------- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/imgui.h b/imgui.h index 9f0e9c8b..bee32fc3 100644 --- a/imgui.h +++ b/imgui.h @@ -1073,7 +1073,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, // [Internal] Combinations and masks - ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX, + ImGuiTableFlags_SizingPolicyMaskX_ = ImGuiTableFlags_SizingPolicyStretchX | ImGuiTableFlags_SizingPolicyFixedX }; // Flags for ImGui::TableSetupColumn() diff --git a/imgui_internal.h b/imgui_internal.h index 054252f2..df026c06 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2278,7 +2278,7 @@ namespace ImGui IMGUI_API void TableDrawContextMenu(ImGuiTable* table); IMGUI_API void TableOpenContextMenu(ImGuiTable* table, int column_n); IMGUI_API void TableReorderDrawChannelsForMerge(ImGuiTable* table); - IMGUI_API void TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* column, bool add_to_existing_sort_orders); + IMGUI_API void TableSetColumnSortDirection(ImGuiTable* table, int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); IMGUI_API void TableSortSpecsBuild(ImGuiTable* table); IMGUI_API void TableBeginRow(ImGuiTable* table); @@ -2292,7 +2292,7 @@ namespace ImGui IMGUI_API void PushTableBackground(); IMGUI_API void PopTableBackground(); - // Tables - Settings + // Tables: Settings IMGUI_API void TableLoadSettings(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table); IMGUI_API ImGuiTableSettings* TableGetBoundSettings(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 9a28a00d..372b1edf 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1998,25 +1998,26 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) return; bool want_separator = false; - const int selected_column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; + const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; + ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL; // Sizing if (table->Flags & ImGuiTableFlags_Resizable) { - if (ImGuiTableColumn* selected_column = (selected_column_n != -1) ? &table->Columns[selected_column_n] : NULL) + if (column != NULL) { - const bool can_resize = !(selected_column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && selected_column->IsVisible; + const bool can_resize = !(column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch)) && column->IsVisible; if (MenuItem("Size column to fit", NULL, false, can_resize)) - TableSetColumnAutofit(table, selected_column_n); + TableSetColumnAutofit(table, column_n); } if (MenuItem("Size all columns to fit", NULL)) { - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { - ImGuiTableColumn* column = &table->Columns[column_n]; - if (column->IsVisible) - TableSetColumnAutofit(table, column_n); + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + if (other_column->IsVisible) + TableSetColumnAutofit(table, other_column_n); } } want_separator = true; @@ -2030,27 +2031,43 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) want_separator = true; } + // Sorting +#if 0 + if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0) + { + if (want_separator) + Separator(); + want_separator = true; + + bool append_to_sort_specs = g.IO.KeyShift; + if (MenuItem("Sort in Ascending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs); + if (MenuItem("Sort in Descending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs); + } +#endif + // Hiding / Visibility if (table->Flags & ImGuiTableFlags_Hideable) { if (want_separator) Separator(); - want_separator = false; + want_separator = true; PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { - ImGuiTableColumn* column = &table->Columns[column_n]; - const char* name = TableGetColumnName(table, column_n); + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + const char* name = TableGetColumnName(table, other_column_n); if (name == NULL) name = ""; // Make sure we can't hide the last active column - bool menu_item_active = (column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; - if (column->IsVisible && table->ColumnsVisibleCount <= 1) + bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; + if (other_column->IsVisible && table->ColumnsVisibleCount <= 1) menu_item_active = false; - if (MenuItem(name, NULL, column->IsVisible, menu_item_active)) - column->IsVisibleNextFrame = !column->IsVisible; + if (MenuItem(name, NULL, other_column->IsVisible, menu_item_active)) + other_column->IsVisibleNextFrame = !other_column->IsVisible; } PopItemFlag(); } @@ -2245,7 +2262,17 @@ void ImGui::TableHeader(const char* label) // Handle clicking on column header to adjust Sort Order if (pressed && table->ReorderColumn != column_n) - TableSortSpecsClickColumn(table, column, g.IO.KeyShift); + { + // Set new sort direction + // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. + // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. + ImGuiSortDirection sort_direction; + if (column->SortOrder == -1) + sort_direction = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; + else + sort_direction = (column->SortDirection == ImGuiSortDirection_Ascending) ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; + TableSetColumnSortDirection(table, column_n, sort_direction, g.IO.KeyShift); + } } // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will @@ -2265,38 +2292,29 @@ void ImGui::TableHeader(const char* label) TableOpenContextMenu(table, column_n); } -void ImGui::TableSortSpecsClickColumn(ImGuiTable* table, ImGuiTableColumn* clicked_column, bool add_to_existing_sort_orders) +// Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert +// the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. +void ImGui::TableSetColumnSortDirection(ImGuiTable* table, int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs) { if (!(table->Flags & ImGuiTableFlags_MultiSortable)) - add_to_existing_sort_orders = false; + append_to_sort_specs = false; ImS8 sort_order_max = 0; - if (add_to_existing_sort_orders) - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - sort_order_max = ImMax(sort_order_max, table->Columns[column_n].SortOrder); + if (append_to_sort_specs) + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) + sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder); - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + ImGuiTableColumn* column = &table->Columns[column_n]; + column->SortDirection = (ImS8)sort_direction; + if (column->SortOrder == -1 || !append_to_sort_specs) + column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0; + + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { - ImGuiTableColumn* column = &table->Columns[column_n]; - if (column == clicked_column) - { - // Set new sort direction and sort order - // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. - // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. - // - Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert - // the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. - if (column->SortOrder == -1) - column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); - else - column->SortDirection = (ImU8)((column->SortDirection == ImGuiSortDirection_Ascending) ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending); - if (column->SortOrder == -1 || !add_to_existing_sort_orders) - column->SortOrder = add_to_existing_sort_orders ? sort_order_max + 1 : 0; - } - else if (!add_to_existing_sort_orders) - { - column->SortOrder = -1; - } - TableFixColumnSortDirection(column); + ImGuiTableColumn* other_column = &table->Columns[other_column_n]; + if (other_column != column && !append_to_sort_specs) + other_column->SortOrder = -1; + TableFixColumnSortDirection(other_column); } table->IsSettingsDirty = true; table->IsSortSpecsDirty = true; From 25b5cc2f9597c268f0029a9d6f85acbd3825a65d Mon Sep 17 00:00:00 2001 From: omar Date: Mon, 28 Sep 2020 17:30:39 +0200 Subject: [PATCH 088/144] Tables: Fixes to support any number of frozen rows (over modifications to clipper code in master) + make clipper run eval after clipect update --- imgui.cpp | 21 ++++++++++++++++----- imgui.h | 1 + imgui_tables.cpp | 4 ++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 823fe725..4b85256d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2237,6 +2237,7 @@ void ImGuiListClipper::Begin(int items_count, float items_height) StartPosY = window->DC.CursorPos.y; ItemsHeight = items_height; ItemsCount = items_count; + ItemsFrozen = 0; StepNo = 0; DisplayStart = -1; DisplayEnd = 0; @@ -2249,7 +2250,7 @@ void ImGuiListClipper::End() // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. if (ItemsCount < INT_MAX && DisplayStart >= 0) - SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); ItemsCount = -1; StepNo = 3; } @@ -2273,12 +2274,22 @@ bool ImGuiListClipper::Step() // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) if (StepNo == 0) { + // While we are in frozen row state, keep displaying items one by one, unclipped + // FIXME: Could be stored as a table-agnostic state. + if (table != NULL && !table->IsFreezeRowsPassed) + { + DisplayStart = ItemsFrozen; + DisplayEnd = ItemsFrozen + 1; + ItemsFrozen++; + return true; + } + StartPosY = window->DC.CursorPos.y; if (ItemsHeight <= 0.0f) { // Submit the first item so we can measure its height (generally it is 0..1) - DisplayStart = 0; - DisplayEnd = 1; + DisplayStart = ItemsFrozen; + DisplayEnd = ItemsFrozen + 1; StepNo = 1; return true; } @@ -2319,7 +2330,7 @@ bool ImGuiListClipper::Step() // Seek cursor if (DisplayStart > already_submitted) - SetCursorPosYAndSetupForPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); + SetCursorPosYAndSetupForPrevLine(StartPosY + (DisplayStart - ItemsFrozen) * ItemsHeight, ItemsHeight); StepNo = 3; return true; @@ -2331,7 +2342,7 @@ bool ImGuiListClipper::Step() { // Seek cursor if (ItemsCount < INT_MAX) - SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor + SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight); // advance cursor ItemsCount = -1; return false; } diff --git a/imgui.h b/imgui.h index bee32fc3..beb4182f 100644 --- a/imgui.h +++ b/imgui.h @@ -2092,6 +2092,7 @@ struct ImGuiListClipper // [Internal] int ItemsCount; int StepNo; + int ItemsFrozen; float ItemsHeight; float StartPosY; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 372b1edf..3e431597 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1774,6 +1774,10 @@ void ImGui::TableEndRow(ImGuiTable* table) column->DrawChannelCurrent = column->DrawChannelRowsAfterFreeze; column->ClipRect.Min.y = r.Min.y; } + + // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y + SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); + table->DrawSplitter.SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); } if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) From 248960d64c9b219bf1f43bd61875a092981b1556 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 30 Sep 2020 22:37:59 +0200 Subject: [PATCH 089/144] Tables: Fix ImGuiTableColumnFlags_WidthAlwaysAutoResize columns when clipped (which would be default behavior without _Resizable and when clipping/scrolling) --- imgui_tables.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 3e431597..b4425db8 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -646,21 +646,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (column->Flags & (ImGuiTableColumnFlags_WidthAlwaysAutoResize | ImGuiTableColumnFlags_WidthFixed)) { - // Latch initial size for fixed columns + // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) count_fixed += 1; - const bool auto_fit = (column->AutoFitQueue != 0x00) || (column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize); - if (auto_fit) - { + if ((column->AutoFitQueue != 0x00) || ((column->Flags & ImGuiTableColumnFlags_WidthAlwaysAutoResize) && !column->IsClipped)) column->WidthRequest = column_width_ideal; - // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets - // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very - // large height (= first frame scrollbar display very off + clipper would skip lots of items). - // This is merely making the side-effect less extreme, but doesn't properly fixes it. - // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? - if (column->AutoFitQueue > 0x01 && table->IsInitializing) - column->WidthRequest = ImMax(column->WidthRequest, min_column_width * 4.0f); - } + // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets + // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very + // large height (= first frame scrollbar display very off + clipper would skip lots of items). + // This is merely making the side-effect less extreme, but doesn't properly fixes it. + // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? + if (column->AutoFitQueue > 0x01 && table->IsInitializing) + column->WidthRequest = ImMax(column->WidthRequest, min_column_width * 4.0f); + sum_width_fixed_requests += column->WidthRequest; } else @@ -2159,7 +2157,7 @@ void ImGui::TableHeadersRow() // Emit a column header (text + optional sort order) // We cpu-clip text here so that all columns headers can be merged into a same draw call. // Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader() -// FIXME-TABLE: Should hold a selection state. +// FIXME-TABLE: Could hold a selection state. // FIXME-TABLE: Style confusion between CellPadding.y and FramePadding.y void ImGui::TableHeader(const char* label) { From b1ebf964f59e97874b06c8fef4ee47e312a310d6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 30 Sep 2020 23:42:37 +0200 Subject: [PATCH 090/144] Tables: Moved TableSetColumnIndex() next to TableNextCell() since they are so similar + made NextCell() crash proof. --- imgui_tables.cpp | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b4425db8..d8681d4b 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1850,6 +1850,8 @@ bool ImGui::TableNextCell() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; + if (!table) + return false; if (table->CurrentColumn != -1 && table->CurrentColumn + 1 < table->ColumnsCount) { @@ -1869,6 +1871,28 @@ bool ImGui::TableNextCell() return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } +bool ImGui::TableSetColumnIndex(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) + return false; + + if (table->CurrentColumn != column_n) + { + if (table->CurrentColumn != -1) + TableEndCell(table); + IM_ASSERT(column_n >= 0 && table->ColumnsCount); + TableBeginCell(table, column_n); + } + + // FIXME-TABLE: Need to clarify if we want to allow IsItemHovered() here + //g.CurrentWindow->DC.LastItemStatusFlags = (column_n == table->HoveredColumn) ? ImGuiItemStatusFlags_HoveredRect : ImGuiItemStatusFlags_None; + + // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. + return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + int ImGui::TableGetColumnCount() { ImGuiContext& g = *GImGui; @@ -1908,28 +1932,6 @@ int ImGui::TableGetColumnIndex() return table->CurrentColumn; } -bool ImGui::TableSetColumnIndex(int column_idx) -{ - ImGuiContext& g = *GImGui; - ImGuiTable* table = g.CurrentTable; - if (!table) - return false; - - if (table->CurrentColumn != column_idx) - { - if (table->CurrentColumn != -1) - TableEndCell(table); - IM_ASSERT(column_idx >= 0 && table->ColumnsCount); - TableBeginCell(table, column_idx); - } - - // FIXME-TABLE: Need to clarify if we want to allow IsItemHovered() here - //g.CurrentWindow->DC.LastItemStatusFlags = (column_n == table->HoveredColumn) ? ImGuiItemStatusFlags_HoveredRect : ImGuiItemStatusFlags_None; - - // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. - return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_idx)) != 0; -} - // Return the cell rectangle based on currently known height. // Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations. // The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it. From 6182973bdeb7acb38c71cb64a58afc3b750a539a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 1 Oct 2020 14:03:25 +0200 Subject: [PATCH 091/144] Tables: (Breaking) Rename TableNextCell() to TableNextColumn(), made TableNextRow() NOT enter into first column. --- imgui.h | 30 ++++++++++++++-------- imgui_demo.cpp | 65 ++++++++++++++++++++++++++---------------------- imgui_tables.cpp | 26 +++++++++++-------- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/imgui.h b/imgui.h index beb4182f..c7516b01 100644 --- a/imgui.h +++ b/imgui.h @@ -653,7 +653,7 @@ namespace ImGui IMGUI_API bool IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0); // return true if the popup is open. // Tables - // [ALPHA API] API will evolve! (see: FIXME-TABLE) + // [ALPHA API] API may evolve! // - Full-featured replacement for old Columns API // - See Demo->Tables for details. // - See ImGuiTableFlags_ and ImGuiTableColumnsFlags_ enums for a description of available flags. @@ -662,28 +662,36 @@ namespace ImGui // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows // - 4. Optionally call TableHeadersRow() to submit a header row (names will be pulled from data submitted to TableSetupColumns) - // - 4. Populate contents - // - In most situations you can use TableNextRow() + TableSetColumnIndex() to start appending into a column. - // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, - // you may prefer using TableNextCell() instead of TableNextRow() + TableSetColumnIndex(). - // - Submit your content with regular ImGui function. + // - 5. Populate contents + // - In most situations you can use TableNextRow() + TableSetColumnIndex(xx) to start appending into a column. + // - If you are using tables as a sort of grid, where every columns is holding the same type of contents, + // you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex(). + // TableNextColumn() will automatically wrap-around into the next row if needed. + // - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column! + // - Summary of possible call flow: + // ---------------------------------------------------------------------------------------------------------- + // TableNextRow() -> TableSetColumnIndex(0) -> Text("Hello 0") -> TableSetColumnIndex(1) -> Text("Hello 1") // OK + // TableNextRow() -> TableNextColumn() Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK + // TableNextColumn() Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK: TableNextColumn() automatically gets to next row! + // TableNextRow() Text("Hello 0") // Not OK! Missing TableSetColumnIndex() or TableNextColumn()! Text will not appear! + // ---------------------------------------------------------------------------------------------------------- // - 5. Call EndTable() #define IMGUI_HAS_TABLE 1 IMGUI_API bool BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f); IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. - IMGUI_API bool TableNextCell(); // append into the next column (next column, or next row if currently in last column). Return true if column is visible. + IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true if column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. IMGUI_API int TableGetColumnIndex(); // return current column index. // Tables: Headers & Columns declaration - // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableHeadersRow() and context-menus. // - Use TableHeadersRow() to create a row and automatically submit a TableHeader() for each column. // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in some advanced cases (e.g. adding custom widgets in header row). - IMGUI_API void TableSetupScrollFreeze(int columns, int rows); + // - Use TableSetupScrollFreeze() to lock columns (from the right) or rows (from the top) so they stay visible when scrolled. IMGUI_API void TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = -1.0f, ImU32 user_id = 0); + IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) // Tables: Miscellaneous functions @@ -694,13 +702,13 @@ namespace ImGui // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) IMGUI_API const char* TableGetColumnName(int column_n = -1); // return NULL if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. - IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextCell() and TableSetColumnIndex(). Pass -1 to use current column. + IMGUI_API bool TableGetColumnIsVisible(int column_n = -1); // return true if column is visible. Same value is also returned by TableNextColumn() and TableSetColumnIndex(). Pass -1 to use current column. IMGUI_API bool TableGetColumnIsSorted(int column_n = -1); // return true if column is included in the sort specs. Rarely used, can be useful to tell if a data change should trigger resort. Equivalent to test ImGuiTableSortSpecs's ->ColumnsMask & (1 << column_n). Pass -1 to use current column. IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). IMGUI_API void TableSetBgColor(ImGuiTableBgTarget bg_target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. - // Columns (Legacy API, prefer using Tables) + // Legacy Columns API (2020: prefer using Tables!) // - You can also use SameLine(pos_x) to mimic simplified columns. IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f95bbffe..bcca3c74 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3348,31 +3348,30 @@ static void ShowDemoWindowTables() // This essentially the same as above, except instead of using a for loop we call TableSetColumnIndex() manually. // Sometimes this makes more sense. - HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, manually."); + HelpMarker("Using TableNextRow() + calling TableNextColumn() _before_ each cell, manually."); if (ImGui::BeginTable("##table2", 3)) { for (int row = 0; row < 4; row++) { ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); + ImGui::TableNextColumn(); ImGui::Text("Row %d", row); - ImGui::TableSetColumnIndex(1); + ImGui::TableNextColumn(); ImGui::Text("Some contents"); - ImGui::TableSetColumnIndex(2); + ImGui::TableNextColumn(); ImGui::Text("123.456"); } ImGui::EndTable(); } - // Another subtle variant, we call TableNextCell() _before_ each cell. At the end of a row, TableNextCell() will create a new row. - // Note that we don't call TableNextRow() here! - // If we want to call TableNextRow(), then we don't need to call TableNextCell() for the first cell. - HelpMarker("Only using TableNextCell(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); + // Another subtle variant, we call TableNextColumn() _before_ each cell. At the end of a row, TableNextColumn() will create a new row. + // Note that we never TableNextRow() here! + HelpMarker("Only using TableNextColumn(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); if (ImGui::BeginTable("##table4", 3)) { for (int item = 0; item < 14; item++) { - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("Item %d", item); } ImGui::EndTable(); @@ -3690,7 +3689,7 @@ static void ShowDemoWindowTables() ImGui::TableNextRow(); for (int column = 0; column < 7; column++) { - // Both TableNextCell() and TableSetColumnIndex() return false when a column is not visible, which can be used for clipping. + // Both TableNextColumn() and TableSetColumnIndex() return false when a column is not visible, which can be used for clipping. if (!ImGui::TableSetColumnIndex(column)) continue; if (column == 0) @@ -3718,7 +3717,7 @@ static void ShowDemoWindowTables() for (int column = 0; column < column_count; column++) { // Make the UI compact because there are so many fields - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); @@ -3780,7 +3779,8 @@ static void ShowDemoWindowTables() ImGui::TableSetupColumn("A1"); ImGui::TableHeadersRow(); - ImGui::TableNextRow(); ImGui::Text("A0 Cell 0"); + ImGui::TableNextColumn(); + ImGui::Text("A0 Cell 0"); { float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeightV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) @@ -3790,20 +3790,22 @@ static void ShowDemoWindowTables() ImGui::TableHeadersRow(); ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); ImGui::Text("B0 Cell 0"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("B0 Cell 1"); ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); ImGui::Text("B1 Cell 0"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("B1 Cell 1"); ImGui::EndTable(); } } - ImGui::TableNextCell(); ImGui::Text("A0 Cell 1"); - ImGui::TableNextRow(); ImGui::Text("A1 Cell 0"); - ImGui::TableNextCell(); ImGui::Text("A1 Cell 1"); + ImGui::TableNextColumn(); ImGui::Text("A0 Cell 1"); + ImGui::TableNextColumn(); ImGui::Text("A1 Cell 0"); + ImGui::TableNextColumn(); ImGui::Text("A1 Cell 1"); ImGui::EndTable(); } ImGui::TreePop(); @@ -3915,6 +3917,7 @@ static void ShowDemoWindowTables() { float min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); + ImGui::TableNextColumn(); ImGui::Text("min_row_height = %.2f", min_row_height); } ImGui::EndTable(); @@ -4003,13 +4006,14 @@ static void ShowDemoWindowTables() static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes) { ImGui::TableNextRow(); + ImGui::TableNextColumn(); const bool is_folder = (node->ChildCount > 0); if (is_folder) { bool open = ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextDisabled("--"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(node->Type); if (open) { @@ -4021,9 +4025,9 @@ static void ShowDemoWindowTables() else { ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanFullWidth); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("%d", node->Size); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(node->Type); } } @@ -4228,13 +4232,13 @@ static void ShowDemoWindowTables() MyItem* item = &items[row_n]; ImGui::PushID(item->ID); ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); + ImGui::TableNextColumn(); ImGui::Text("%04d", item->ID); - ImGui::TableSetColumnIndex(1); + ImGui::TableNextColumn(); ImGui::TextUnformatted(item->Name); - ImGui::TableSetColumnIndex(2); + ImGui::TableNextColumn(); ImGui::SmallButton("None"); - ImGui::TableSetColumnIndex(3); + ImGui::TableNextColumn(); ImGui::Text("%d", item->Quantity); ImGui::PopID(); } @@ -4431,6 +4435,7 @@ static void ShowDemoWindowTables() const bool item_is_selected = selection.contains(item->ID); ImGui::PushID(item->ID); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); + ImGui::TableNextColumn(); // For the demo purpose we can select among different type of items submitted in the first column char label[32]; @@ -4462,14 +4467,14 @@ static void ShowDemoWindowTables() } } - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(item->Name); // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity, // and we are currently sorting on the column showing the Quantity. // To avoid triggering a sort while holding the button, we only trigger it when the button has been released. // You will probably need a more advanced system in your code if you want to automatically sort when a specific entry changes. - if (ImGui::TableNextCell()) + if (ImGui::TableNextColumn()) { if (ImGui::SmallButton("Chop")) { item->Quantity += 1; } if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } @@ -4478,16 +4483,16 @@ static void ShowDemoWindowTables() if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } } - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("%d", item->Quantity); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); if (show_wrapped_text) ImGui::TextWrapped("Lorem ipsum dolor sit amet"); else ImGui::Text("Lorem ipsum dolor sit amet"); - ImGui::TableNextCell(); + ImGui::TableNextColumn(); ImGui::Text("1234"); ImGui::PopID(); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d8681d4b..df7d9dda 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -79,7 +79,7 @@ // | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction // | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu // - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) -// - TableNextRow() / TableNextCell() user begin into the first row, also automatically called by TableHeadersRow() +// - TableNextRow() / TableNextColumn() user begin into the first row, also automatically called by TableHeadersRow() // | TableEndCell() - close existing cell if not the first time // | TableBeginCell() - enter into current cell // - [...] user emit contents @@ -345,7 +345,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } table->RefScale = new_ref_scale_unit; - // Disable output until user calls TableNextRow() or TableNextCell() leading to the TableUpdateLayout() call.. + // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. inner_window->SkipItems = true; @@ -988,7 +988,7 @@ void ImGui::EndTable() // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); - // If the user never got to call TableNextRow() or TableNextCell(), we call layout ourselves to ensure all our + // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. if (!table->IsLayoutLocked) TableUpdateLayout(table); @@ -1610,7 +1610,8 @@ void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) table->RowPosY2 += table->CellPaddingY * 2.0f; table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); - TableBeginCell(table, 0); + // Disable output until user calls TableNextColumn() + table->InnerWindow->SkipItems = true; } // [Internal] @@ -1654,7 +1655,8 @@ void ImGui::TableEndRow(ImGuiTable* table) IM_ASSERT(window == table->InnerWindow); IM_ASSERT(table->IsInsideRow); - TableEndCell(table); + if (table->CurrentColumn != -1) + TableEndCell(table); // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. @@ -1783,7 +1785,7 @@ void ImGui::TableEndRow(ImGuiTable* table) table->IsInsideRow = false; } -// [Internal] Called by TableNextCell()! +// [Internal] Called by TableNextColumn()! // This is called very frequently, so we need to be mindful of unnecessary overhead. // FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. void ImGui::TableBeginCell(ImGuiTable* table, int column_n) @@ -1824,7 +1826,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) } } -// [Internal] Called by TableNextRow()/TableNextCell()! +// [Internal] Called by TableNextRow()/TableNextColumn()! void ImGui::TableEndCell(ImGuiTable* table) { ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; @@ -1846,28 +1848,30 @@ void ImGui::TableEndCell(ImGuiTable* table) // Append into the next cell // FIXME-TABLE: Wrapping to next row should be optional? -bool ImGui::TableNextCell() +bool ImGui::TableNextColumn() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; if (!table) return false; - if (table->CurrentColumn != -1 && table->CurrentColumn + 1 < table->ColumnsCount) + if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) { - TableEndCell(table); + if (table->CurrentColumn != -1) + TableEndCell(table); TableBeginCell(table, table->CurrentColumn + 1); } else { TableNextRow(); + TableBeginCell(table, 0); } - int column_n = table->CurrentColumn; // FIXME-TABLE: Need to clarify if we want to allow IsItemHovered() here //g.CurrentWindow->DC.LastItemStatusFlags = (column_n == table->HoveredColumn) ? ImGuiItemStatusFlags_HoveredRect : ImGuiItemStatusFlags_None; // FIXME-TABLE: it is likely to alter layout if user skips a columns contents based on clipping. + int column_n = table->CurrentColumn; return (table->VisibleUnclippedMaskByIndex & ((ImU64)1 << column_n)) != 0; } From e66b28693ad94f59f9da09a19ede4d3d1e5a3280 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 1 Oct 2020 19:54:10 +0200 Subject: [PATCH 092/144] Tables: Added ImGuiTableFlags_ContextMenuInBody flag. Worked to get TableOpenContextMenu() in public API but kept it internal. --- imgui.h | 29 +++++++++++---------- imgui_demo.cpp | 68 +++++++++++++++++++++++++++++++++++++----------- imgui_internal.h | 2 +- imgui_tables.cpp | 19 +++++++++++--- 4 files changed, 85 insertions(+), 33 deletions(-) diff --git a/imgui.h b/imgui.h index c7516b01..8f4ba992 100644 --- a/imgui.h +++ b/imgui.h @@ -1056,28 +1056,29 @@ enum ImGuiTableFlags_ ImGuiTableFlags_Sortable = 1 << 3, // Allow sorting on one column (sort_specs_count will always be == 1). Call TableGetSortSpecs() to obtain sort specs. ImGuiTableFlags_MultiSortable = 1 << 4, // Allow sorting on multiple columns by holding Shift (sort_specs_count may be > 1). Call TableGetSortSpecs() to obtain sort specs. ImGuiTableFlags_NoSavedSettings = 1 << 5, // Disable persisting columns order, width and sort settings in the .ini file. + ImGuiTableFlags_ContextMenuInBody = 1 << 6, // Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow(). // Decoration - ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent to calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) - ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. - ImGuiTableFlags_BordersOuterH = 1 << 8, // Draw horizontal borders at the top and bottom. - ImGuiTableFlags_BordersInnerV = 1 << 9, // Draw vertical borders between columns. - ImGuiTableFlags_BordersOuterV = 1 << 10, // Draw vertical borders on the left and right sides. + ImGuiTableFlags_RowBg = 1 << 7, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent to calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually) + ImGuiTableFlags_BordersInnerH = 1 << 8, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersOuterH = 1 << 9, // Draw horizontal borders at the top and bottom. + ImGuiTableFlags_BordersInnerV = 1 << 10, // Draw vertical borders between columns. + ImGuiTableFlags_BordersOuterV = 1 << 11, // Draw vertical borders on the left and right sides. ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH, // Draw horizontal borders. ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV, // Draw vertical borders. ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. - ImGuiTableFlags_BordersFullHeightV = 1 << 11, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. + ImGuiTableFlags_BordersFullHeightV = 1 << 12, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. // Padding, Sizing - ImGuiTableFlags_SizingPolicyFixedX = 1 << 12, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. - ImGuiTableFlags_SizingPolicyStretchX = 1 << 13, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. - ImGuiTableFlags_NoHeadersWidth = 1 << 14, // Disable header width contribution to automatic width calculation. - ImGuiTableFlags_NoHostExtendY = 1 << 15, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) - ImGuiTableFlags_NoKeepColumnsVisible = 1 << 16, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. - ImGuiTableFlags_NoClip = 1 << 17, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options. + ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. + ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. + ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. + ImGuiTableFlags_NoClip = 1 << 18, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options. // Scrolling - ImGuiTableFlags_ScrollX = 1 << 20, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. - ImGuiTableFlags_ScrollY = 1 << 21, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + ImGuiTableFlags_ScrollX = 1 << 19, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 20, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, // [Internal] Combinations and masks diff --git a/imgui_demo.cpp b/imgui_demo.cpp index bcca3c74..3961148f 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -4104,54 +4104,91 @@ static void ShowDemoWindowTables() ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Context menus")) { - HelpMarker("By default, TableHeadersRow()/TableHeader() will open a context-menu on right-click."); - ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders; + HelpMarker("By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\nUsing ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body."); + static ImGuiTableFlags flags1 = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_ContextMenuInBody; + ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", (unsigned int*)&flags1, ImGuiTableFlags_ContextMenuInBody); + + // Context Menus: first example + // [1.1] Right-click on the TableHeadersRow() line to open the default table context menu. + // [1.2] Right-click in columns also open the default table context menu (if ImGuiTableFlags_ContextMenuInBody is set) const int COLUMNS_COUNT = 3; - if (ImGui::BeginTable("##table1", COLUMNS_COUNT, flags)) + if (ImGui::BeginTable("##table1", COLUMNS_COUNT, flags1)) { ImGui::TableSetupColumn("One"); ImGui::TableSetupColumn("Two"); ImGui::TableSetupColumn("Three"); - // Context Menu 1: right-click on header (including empty section after the third column!) should open Default Table Popup + // [1.1]] Right-click on the TableHeadersRow() line to open the default table context menu. ImGui::TableHeadersRow(); + + // Submit dummy contents for (int row = 0; row < 4; row++) { ImGui::TableNextRow(); for (int column = 0; column < COLUMNS_COUNT; column++) { ImGui::TableSetColumnIndex(column); - ImGui::PushID(row * COLUMNS_COUNT + column); + ImGui::Text("Cell %d,%d", 0, row); + } + } + ImGui::EndTable(); + } + + // Context Menus: second example + // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu. + // [2.2] Right-click on the ".." to open a custom popup + // [2.3] Right-click in columns to open another custom popup + HelpMarker("Demonstrate mixing table context menu (over header), item context button (over button) and custom per-colum context menu (over column body)."); + ImGuiTableFlags flags2 = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders; + if (ImGui::BeginTable("##table2", COLUMNS_COUNT, flags2)) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + + // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu. + ImGui::TableHeadersRow(); + for (int row = 0; row < 4; row++) + { + ImGui::TableNextRow(); + for (int column = 0; column < COLUMNS_COUNT; column++) + { + // Submit dummy contents + ImGui::TableSetColumnIndex(column); ImGui::Text("Cell %d,%d", column, row); ImGui::SameLine(); - // Context Menu 2: right-click on buttons open Custom Button Popup + // [2.2] Right-click on the ".." to open a custom popup + ImGui::PushID(row * COLUMNS_COUNT + column); ImGui::SmallButton(".."); if (ImGui::BeginPopupContextItem()) { - ImGui::Text("This is the popup for Button() On Cell %d,%d", column, row); - ImGui::Selectable("Close"); + ImGui::Text("This is the popup for Button(\"..\") in Cell %d,%d", column, row); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } ImGui::PopID(); } } - // Context Menu 3: Right-click anywhere in columns opens a custom popup - // We use the ImGuiPopupFlags_NoOpenOverExistingPopup flag to avoid displaying over either the standard TableHeader context-menu or the Button context-menu. + // [2.3] Right-click anywhere in columns to open another custom popup + // (instead of testing for !IsAnyItemHovered() we could also call OpenPopup() with ImGuiPopupFlags_NoOpenOverExistingPopup + // to manage popup priority as the popups triggers, here "are we hovering a column" are overlapping) const int hovered_column = ImGui::TableGetHoveredColumn(); for (int column = 0; column < COLUMNS_COUNT + 1; column++) { ImGui::PushID(column); - if (hovered_column == column && ImGui::IsMouseReleased(1)) - ImGui::OpenPopup("MyPopup", ImGuiPopupFlags_NoOpenOverExistingPopup); + if (hovered_column == column && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(1)) + ImGui::OpenPopup("MyPopup"); if (ImGui::BeginPopup("MyPopup")) { if (column == COLUMNS_COUNT) - ImGui::Text("This is the popup for unused space after the last column."); + ImGui::Text("This is a custom popup for unused space after the last column."); else - ImGui::Text("This is the popup for Column '%s'", ImGui::TableGetColumnName(column)); - ImGui::Selectable("Close"); + ImGui::Text("This is a custom popup for Column %d", column); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } ImGui::PopID(); @@ -4289,6 +4326,7 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_Sortable", (unsigned int*)&flags, ImGuiTableFlags_Sortable); ImGui::CheckboxFlags("ImGuiTableFlags_MultiSortable", (unsigned int*)&flags, ImGuiTableFlags_MultiSortable); ImGui::CheckboxFlags("ImGuiTableFlags_NoSavedSettings", (unsigned int*)&flags, ImGuiTableFlags_NoSavedSettings); + ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", (unsigned int*)&flags, ImGuiTableFlags_ContextMenuInBody); ImGui::Unindent(); ImGui::BulletText("Decoration:"); diff --git a/imgui_internal.h b/imgui_internal.h index df026c06..1bf8a164 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2276,7 +2276,7 @@ namespace ImGui IMGUI_API void TableSetColumnWidth(ImGuiTable* table, ImGuiTableColumn* column, float width); IMGUI_API void TableDrawBorders(ImGuiTable* table); IMGUI_API void TableDrawContextMenu(ImGuiTable* table); - IMGUI_API void TableOpenContextMenu(ImGuiTable* table, int column_n); + IMGUI_API void TableOpenContextMenu(int column_n = -1); IMGUI_API void TableReorderDrawChannelsForMerge(ImGuiTable* table); IMGUI_API void TableSetColumnSortDirection(ImGuiTable* table, int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index df7d9dda..46ada6f3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1002,6 +1002,11 @@ void ImGui::EndTable() if (table->IsInsideRow) TableEndRow(table); + // Context menu in columns body + if (flags & ImGuiTableFlags_ContextMenuInBody) + if (table->HoveredColumnBody != -1 && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) + TableOpenContextMenu((int)table->HoveredColumnBody); + // Finalize table height inner_window->SkipItems = table->HostSkipItems; inner_window->DC.CursorMaxPos = table->HostCursorMaxPos; @@ -2040,6 +2045,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) } // Sorting + // (modify TableOpenContextMenu() to add _Sortable flag if enabling this) #if 0 if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0) { @@ -2082,8 +2088,14 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table) } // Use -1 to open menu not specific to a given column. -void ImGui::TableOpenContextMenu(ImGuiTable* table, int column_n) +void ImGui::TableOpenContextMenu(int column_n) { + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (column_n == -1 && table->CurrentColumn != -1) // When called within a column automatically use this one (for consistency) + column_n = table->CurrentColumn; + if (column_n == table->ColumnsCount) // To facilitate using with TableGetHoveredColumn() + column_n = -1; IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount); if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { @@ -2157,7 +2169,7 @@ void ImGui::TableHeadersRow() ImVec2 mouse_pos = ImGui::GetMousePos(); if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height) - TableOpenContextMenu(table, -1); // Will open a non-column-specific popup. + TableOpenContextMenu(-1); // Will open a non-column-specific popup. } // Emit a column header (text + optional sort order) @@ -2297,7 +2309,7 @@ void ImGui::TableHeader(const char* label) // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden if (IsMouseReleased(1) && IsItemHovered()) - TableOpenContextMenu(table, column_n); + TableOpenContextMenu(column_n); } // Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert @@ -2359,6 +2371,7 @@ bool ImGui::TableGetColumnIsSorted(int column_n) return (column->SortOrder != -1); } +// Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. int ImGui::TableGetHoveredColumn() { ImGuiContext& g = *GImGui; From 2ee20fdb7c72aa6e886211eb850857c35be3f772 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 5 Oct 2020 15:20:28 +0200 Subject: [PATCH 093/144] Tables: Frozen rows/columns in nav menu layer, fixed conflict between column id and holding child id. --- imgui_internal.h | 3 ++- imgui_tables.cpp | 40 ++++++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 1bf8a164..ea45d384 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1916,6 +1916,7 @@ struct ImGuiTableColumn bool IsVisibleNextFrame; bool IsClipped; // Set when not overlapping the host window clipping rectangle. bool SkipItems; + ImGuiNavLayer NavLayerCurrent; ImS8 DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) ImS8 IndexWithinVisibleSet; // Index within visible set (<= IndexToDisplayOrder) ImS8 DrawChannelCurrent; // Index within DrawSplitter.Channels[] @@ -2039,7 +2040,7 @@ struct ImGuiTable bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) bool IsResetDisplayOrderRequest; - bool IsFreezeRowsPassed; // Set when we got past the frozen row (the first one). + bool IsFreezeRowsPassed; // Set when we got past the frozen row. bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis ImGuiTable() diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 46ada6f3..7143b9f0 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -235,12 +235,13 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f)); - // Create scrolling region (without border = zero window padding) + // Create scrolling region (without border and zero window padding) ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; BeginChildEx(name, instance_id, table->OuterRect.GetSize(), false, child_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); + IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f); } // Push a standardized ID for both child and not-child using tables, equivalent to BeginTable() doing PushID(label) matching @@ -479,7 +480,7 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); - IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit @@ -487,7 +488,7 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImS8)rows : 0; table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; - table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); + table->IsFreezeRowsPassed = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b } void ImGui::TableUpdateDrawChannels(ImGuiTable* table) @@ -778,6 +779,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; + column->NavLayerCurrent = (table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main; + if (table->FreezeColumnsCount > 0 && table->FreezeColumnsCount == visible_n) offset_x += work_rect.Min.x - table->OuterRect.Min.x; @@ -1671,12 +1674,12 @@ void ImGui::TableEndRow(ImGuiTable* table) const float bg_y1 = table->RowPosY1; const float bg_y2 = table->RowPosY2; - const bool unfreeze_rows = (table->CurrentRow + 1 == table->FreezeRowsCount && table->FreezeRowsCount > 0); - + const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount); + const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest); if (table->CurrentRow == 0) table->LastFirstRowHeight = bg_y2 - bg_y1; - const bool is_visible = table->CurrentRow >= 0 && bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y; + const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y); if (is_visible) { // Decide of background color for the row @@ -1710,7 +1713,7 @@ void ImGui::TableEndRow(ImGuiTable* table) } const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; - const bool draw_strong_bottom_border = unfreeze_rows;// || (table->RowFlags & ImGuiTableRowFlags_Headers); + const bool draw_strong_bottom_border = unfreeze_rows_actual;// || (table->RowFlags & ImGuiTableRowFlags_Headers); if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) { // In theory we could call SetWindowClipRectBeforeChannelChange() but since we know TableEndRow() is @@ -1757,18 +1760,22 @@ void ImGui::TableEndRow(ImGuiTable* table) // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and // get the new cursor position. - if (unfreeze_rows) + if (unfreeze_rows_request) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->NavLayerCurrent = (column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main; + } + if (unfreeze_rows_actual) { IM_ASSERT(table->IsFreezeRowsPassed == false); table->IsFreezeRowsPassed = true; table->DrawSplitter.SetCurrentChannel(window->DrawList, 0); - ImRect r; - r.Min.x = table->InnerClipRect.Min.x; - r.Min.y = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); - r.Max.x = table->InnerClipRect.Max.x; - r.Max.y = window->InnerClipRect.Max.y; - table->BackgroundClipRect = r; + // BackgroundClipRect starts as table->InnerClipRect, reduce it now + float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); + table->BackgroundClipRect.Min.y = y0; + table->BackgroundClipRect.Max.y = window->InnerClipRect.Max.y; float row_height = table->RowPosY2 - table->RowPosY1; table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; @@ -1777,7 +1784,7 @@ void ImGui::TableEndRow(ImGuiTable* table) { ImGuiTableColumn* column = &table->Columns[column_n]; column->DrawChannelCurrent = column->DrawChannelRowsAfterFreeze; - column->ClipRect.Min.y = r.Min.y; + column->ClipRect.Min.y = table->BackgroundClipRect.Min.y; } // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y @@ -1810,6 +1817,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; window->DC.LastItemId = 0; + window->DC.NavLayerCurrent = column->NavLayerCurrent; window->WorkRect.Min.y = window->DC.CursorPos.y; window->WorkRect.Min.x = column->MinX + table->CellPaddingX1; @@ -1964,7 +1972,7 @@ const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no) { IM_ASSERT(column_n < table->ColumnsCount); - ImGuiID id = table->ID + (instance_no * table->ColumnsCount) + column_n; + ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n; return id; } From 172704c079233839a805a805cda6808e44e012e6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 6 Oct 2020 15:08:27 +0200 Subject: [PATCH 094/144] Tables: Add demo code. Remove dead code + seemingly duplicate border in TableDrawBorders(). --- imgui.h | 2 +- imgui_demo.cpp | 37 +++++++++++++++++++++++++++++-------- imgui_tables.cpp | 22 ++++------------------ 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/imgui.h b/imgui.h index 8f4ba992..18efa03b 100644 --- a/imgui.h +++ b/imgui.h @@ -1069,7 +1069,7 @@ enum ImGuiTableFlags_ ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. ImGuiTableFlags_BordersFullHeightV = 1 << 12, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. - // Padding, Sizing + // Sizing, Padding ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3961148f..9a9cbed4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3385,8 +3385,12 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Borders, background")) { // Expose a few Borders related flags interactively + enum ContentsType { CT_Text, CT_FillButton }; static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; + static bool display_headers = false; static bool display_width = false; + static int contents_type = CT_Text; + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&flags, ImGuiTableFlags_Borders); ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH"); @@ -3402,15 +3406,30 @@ static void ShowDemoWindowTables() ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersFullHeightV", (unsigned int*)&flags, ImGuiTableFlags_BordersFullHeightV); ImGui::SameLine(); HelpMarker("Makes a difference when headers are enabled"); ImGui::Unindent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", (unsigned int*)&flags, ImGuiTableFlags_BordersInner); ImGui::Unindent(); - ImGui::Checkbox("Debug Display width", &display_width); + ImGui::AlignTextToFramePadding(); ImGui::Text("Cell contents:"); + ImGui::SameLine(); ImGui::RadioButton("Text", &contents_type, CT_Text); + ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); + ImGui::Checkbox("Display headers", &display_headers); + ImGui::Checkbox("Display debug width", &display_width); if (ImGui::BeginTable("##table1", 3, flags)) { + // Display headers so we can inspect their interaction with borders. + // (Headers are not the main purpose of this section of the demo, so we are not elaborating on them too much. See other sections for details) + if (display_headers) + { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + } + for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -3420,7 +3439,7 @@ static void ShowDemoWindowTables() char buf[32]; if (display_width) { - // [DEBUG] Draw limits + // [DEBUG] Draw limits FIXME-TABLE: Move to Advanced section ImVec2 p = ImGui::GetCursorScreenPos(); float contents_x1 = p.x; float contents_x2 = ImGui::GetWindowPos().x + ImGui::GetContentRegionMax().x; @@ -3437,7 +3456,11 @@ static void ShowDemoWindowTables() { sprintf(buf, "Hello %d,%d", row, column); } - ImGui::TextUnformatted(buf); + + if (contents_type == CT_Text) + ImGui::TextUnformatted(buf); + else if (contents_type) + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); } } ImGui::EndTable(); @@ -4305,7 +4328,6 @@ static void ShowDemoWindowTables() static float row_min_height = 0.0f; // Auto static float inner_width_with_scroll = 0.0f; // Auto-extend static bool outer_size_enabled = true; - static bool lock_first_column_visibility = false; static bool show_headers = true; static bool show_wrapped_text = false; //static ImGuiTextFilter filter; @@ -4370,6 +4392,8 @@ static void ShowDemoWindowTables() ImGui::BulletText("Other:"); ImGui::Indent(); + ImGui::Checkbox("show_headers", &show_headers); + ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); ImGui::DragFloat2("##OuterSize", &outer_size_value.x); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::Checkbox("outer_size", &outer_size_enabled); @@ -4384,9 +4408,6 @@ static void ShowDemoWindowTables() ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000); ImGui::Combo("contents_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); //filter.Draw("filter"); - ImGui::Checkbox("show_headers", &show_headers); - ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); - ImGui::Checkbox("lock_first_column_visibility", &lock_first_column_visibility); ImGui::Unindent(); ImGui::PopItemWidth(); @@ -4424,7 +4445,7 @@ static void ShowDemoWindowTables() // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); - ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | (lock_first_column_visibility ? ImGuiTableColumnFlags_NoHide : 0), -1.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHide, -1.0f, MyItemColumnID_ID); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Name); ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, -1.0f, MyItemColumnID_Action); ImGui::TableSetupColumn("Quantity Long Label", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 1.0f, MyItemColumnID_Quantity);// , ImGuiTableColumnFlags_None | ImGuiTableColumnFlags_WidthAlwaysAutoResize); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 7143b9f0..e7cc2b58 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1147,7 +1147,8 @@ void ImGui::TableDrawBorders(ImGuiTable* table) float draw_y2_base = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight; float draw_y2_full = table->OuterRect.Max.y; ImU32 border_base_col; - if (!table->IsUsingHeaders || (table->Flags & ImGuiTableFlags_BordersFullHeightV)) + const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersFullHeightV); + if (borders_full_height) { draw_y2_base = draw_y2_full; border_base_col = table->BorderColorLight; @@ -1157,9 +1158,6 @@ void ImGui::TableDrawBorders(ImGuiTable* table) border_base_col = table->BorderColorStrong; } - if ((table->Flags & ImGuiTableFlags_BordersOuterV) && (table->InnerWindow == table->OuterWindow)) - inner_drawlist->AddLine(ImVec2(table->OuterRect.Min.x, draw_y1), ImVec2(table->OuterRect.Min.x, draw_y2_base), border_base_col, border_size); - if (table->Flags & ImGuiTableFlags_BordersInnerV) { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) @@ -1696,24 +1694,12 @@ void ImGui::TableEndRow(ImGuiTable* table) ImU32 border_col = 0; const float border_size = TABLE_BORDER_SIZE; if (table->CurrentRow != 0 || table->InnerWindow == table->OuterWindow) - { if (table->Flags & ImGuiTableFlags_BordersInnerH) - { - //if (table->CurrentRow == 0 && table->InnerWindow == table->OuterWindow) - // border_col = table->BorderOuterColor; - //else - if (table->CurrentRow > 0)// && !(table->LastRowFlags & ImGuiTableRowFlags_Headers)) + if (table->CurrentRow > 0) border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; - } - else - { - //if (table->RowFlags & ImGuiTableRowFlags_Headers) - // border_col = table->BorderOuterColor; - } - } const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; - const bool draw_strong_bottom_border = unfreeze_rows_actual;// || (table->RowFlags & ImGuiTableRowFlags_Headers); + const bool draw_strong_bottom_border = unfreeze_rows_actual; if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) { // In theory we could call SetWindowClipRectBeforeChannelChange() but since we know TableEndRow() is From 02b27b75a40067d1671c37a57423efd2e63cd5b9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 6 Oct 2020 17:53:59 +0200 Subject: [PATCH 095/144] Tables: Added ImGuiTableFlags_NoBordersInBody, ImGuiTableFlags_NoBordersInBodyUntilResize, removed ImGuiTableFlags_BordersFullHeightV. --- imgui.h | 19 +++++++------- imgui_demo.cpp | 21 +++++++++------ imgui_tables.cpp | 67 ++++++++++++++++++++++++++---------------------- 3 files changed, 59 insertions(+), 48 deletions(-) diff --git a/imgui.h b/imgui.h index 18efa03b..02879546 100644 --- a/imgui.h +++ b/imgui.h @@ -1068,17 +1068,18 @@ enum ImGuiTableFlags_ ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. - ImGuiTableFlags_BordersFullHeightV = 1 << 12, // Borders covers all rows even when Headers are being used. Allow resizing from any rows. + ImGuiTableFlags_NoBordersInBody = 1 << 12, // Disable vertical borders in columns Body (borders will always appears in Headers). + ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 13, // Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers). // Sizing, Padding - ImGuiTableFlags_SizingPolicyFixedX = 1 << 13, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. - ImGuiTableFlags_SizingPolicyStretchX = 1 << 14, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. - ImGuiTableFlags_NoHeadersWidth = 1 << 15, // Disable header width contribution to automatic width calculation. - ImGuiTableFlags_NoHostExtendY = 1 << 16, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) - ImGuiTableFlags_NoKeepColumnsVisible = 1 << 17, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. - ImGuiTableFlags_NoClip = 1 << 18, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options. + ImGuiTableFlags_SizingPolicyFixedX = 1 << 14, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. + ImGuiTableFlags_SizingPolicyStretchX = 1 << 15, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. + ImGuiTableFlags_NoHeadersWidth = 1 << 16, // Disable header width contribution to automatic width calculation. + ImGuiTableFlags_NoHostExtendY = 1 << 17, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) + ImGuiTableFlags_NoKeepColumnsVisible = 1 << 18, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. + ImGuiTableFlags_NoClip = 1 << 19, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options. // Scrolling - ImGuiTableFlags_ScrollX = 1 << 19, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. - ImGuiTableFlags_ScrollY = 1 << 20, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + ImGuiTableFlags_ScrollX = 1 << 20, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 21, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. ImGuiTableFlags_Scroll = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY, // [Internal] Combinations and masks diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 9a9cbed4..daa3ad46 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3406,12 +3406,14 @@ static void ShowDemoWindowTables() ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersFullHeightV", (unsigned int*)&flags, ImGuiTableFlags_BordersFullHeightV); ImGui::SameLine(); HelpMarker("Makes a difference when headers are enabled"); ImGui::Unindent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", (unsigned int*)&flags, ImGuiTableFlags_BordersInner); ImGui::Unindent(); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); + ImGui::AlignTextToFramePadding(); ImGui::Text("Cell contents:"); ImGui::SameLine(); ImGui::RadioButton("Text", &contents_type, CT_Text); ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); @@ -3575,10 +3577,12 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Reorderable, hideable, with headers")) { HelpMarker("Click and drag column headers to reorder columns.\n\nYou can also right-click on a header to open a context menu."); - static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody; ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBody); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBodyUntilResize); if (ImGui::BeginTable("##table1", 3, flags)) { @@ -3796,7 +3800,7 @@ static void ShowDemoWindowTables() { HelpMarker("This demonstrate embedding a table into another table cell."); - if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeightV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("recurse1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("A0"); ImGui::TableSetupColumn("A1"); @@ -3806,7 +3810,7 @@ static void ShowDemoWindowTables() ImGui::Text("A0 Cell 0"); { float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; - if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_BordersFullHeightV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) + if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("B0"); ImGui::TableSetupColumn("B1"); @@ -4007,7 +4011,7 @@ static void ShowDemoWindowTables() ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Tree view")) { - static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg; + static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; //ImGui::CheckboxFlags("ImGuiTableFlags_Scroll", (unsigned int*)&flags, ImGuiTableFlags_Scroll); if (ImGui::BeginTable("##3ways", 3, flags)) @@ -4254,7 +4258,7 @@ static void ShowDemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable - | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV + | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY; if (ImGui::BeginTable("##table", 4, flags, ImVec2(0, 250), 0.0f)) { @@ -4313,7 +4317,7 @@ static void ShowDemoWindowTables() { static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable - | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders + | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingPolicyFixedX ; @@ -4360,7 +4364,8 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersFullHeightV", (unsigned int*)&flags, ImGuiTableFlags_BordersFullHeightV); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); ImGui::Unindent(); ImGui::BulletText("Padding, Sizing:"); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e7cc2b58..c607a8b0 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -118,6 +118,10 @@ inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags) if ((flags & ImGuiTableFlags_NoHostExtendY) && (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0) flags &= ~ImGuiTableFlags_NoHostExtendY; + // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody + if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize) + flags &= ~ImGuiTableFlags_NoBordersInBody; + return flags; } @@ -938,11 +942,10 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). // Actual columns highlight/render will be performed in EndTable() and not be affected. - const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersFullHeightV); const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; const float hit_y1 = table->OuterRect.Min.y; - const float hit_y2_full = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); - const float hit_y2 = borders_full_height ? hit_y2_full : (hit_y1 + table->LastFirstRowHeight); + const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight); + const float hit_y2_head = hit_y1 + table->LastFirstRowHeight; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { @@ -954,8 +957,13 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) continue; + // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() + const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body; + if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) + continue; + ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); - ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, hit_y2); + ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); KeepAliveID(column_id); @@ -1144,19 +1152,8 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw inner border and resizing feedback const float border_size = TABLE_BORDER_SIZE; const float draw_y1 = table->OuterRect.Min.y; - float draw_y2_base = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight; - float draw_y2_full = table->OuterRect.Max.y; - ImU32 border_base_col; - const bool borders_full_height = (table->IsUsingHeaders == false) || (table->Flags & ImGuiTableFlags_BordersFullHeightV); - if (borders_full_height) - { - draw_y2_base = draw_y2_full; - border_base_col = table->BorderColorLight; - } - else - { - border_base_col = table->BorderColorStrong; - } + const float draw_y2_body = table->OuterRect.Max.y; + const float draw_y2_head = table->IsUsingHeaders ? ((table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) { @@ -1170,23 +1167,31 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const bool is_hovered = (table->HoveredColumnBorder == column_n); const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; - bool draw_right_border = (column->MaxX <= table->InnerClipRect.Max.x) || (is_resized || is_hovered); + + if (column->MaxX > table->InnerClipRect.Max.x && !is_resized && is_hovered) + continue; if (column->NextVisibleColumn == -1 && !is_resizable) - draw_right_border = false; - if (draw_right_border && column->MaxX > column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. + continue; + if (column->MaxX <= column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. + continue; + + // Draw in outer window so right-most column won't be clipped + // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling. + ImU32 col; + float draw_y2; + if (is_hovered || is_resized || (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1)) { - // Draw in outer window so right-most column won't be clipped - // Always draw full height border when: - // - not using headers - // - user specify ImGuiTableFlags_BordersFullHeight - // - being interacted with - // - on the delimitation of frozen column scrolling - const ImU32 col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : border_base_col; - float draw_y2 = draw_y2_base; - if (is_hovered || is_resized || (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1)) - draw_y2 = draw_y2_full; - inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); + draw_y2 = draw_y2_body; + col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : table->BorderColorStrong; } + else + { + draw_y2 = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body; + col = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? table->BorderColorStrong : table->BorderColorLight; + } + + if (draw_y2 > draw_y1) + inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); } } From 77e561aaf3e556fe72a60fc4cd4bfebcf54e1f13 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 7 Oct 2020 16:33:33 +0200 Subject: [PATCH 096/144] Tables: Made demo options consistently compact, replaced constants with font-based sizes, added comments on memory allocations. --- imgui_demo.cpp | 265 ++++++++++++++++++++++++++++------------------- imgui_tables.cpp | 10 +- 2 files changed, 166 insertions(+), 109 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index daa3ad46..5a8230ef 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3287,12 +3287,29 @@ struct MyItem const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; } +// Make the UI compact because there are so many fields +static void PushStyleCompact() +{ + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.70f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.70f))); +} + +static void PopStyleCompact() +{ + ImGui::PopStyleVar(2); +} + static void ShowDemoWindowTables() { //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (!ImGui::CollapsingHeader("Tables & Columns")) return; + // Using those as a base value to create width/height that are factor of the size of our font + const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; + const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + ImGui::PushID("Tables"); int open_action = -1; @@ -3327,7 +3344,7 @@ static void ShowDemoWindowTables() ImGui::SetNextItemOpen(open_action != 0); if (ImGui::TreeNode("Basic")) { - // Here we will showcase 4 different ways to output a table. They are very simple variations of a same thing! + // Here we will showcase three different ways to output a table. They are very simple variations of a same thing! // Basic use of tables using TableNextRow() to create a new row, and TableSetColumnIndex() to select the column. // In many situations, this is the most flexible and easy to use pattern. @@ -3366,7 +3383,9 @@ static void ShowDemoWindowTables() // Another subtle variant, we call TableNextColumn() _before_ each cell. At the end of a row, TableNextColumn() will create a new row. // Note that we never TableNextRow() here! - HelpMarker("Only using TableNextColumn(), which tends to be convenient for tables where every cells contains the same type of contents.\nThis is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); + HelpMarker( + "Only using TableNextColumn(), which tends to be convenient for tables where every cells contains the same type of contents.\n" + "This is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); if (ImGui::BeginTable("##table4", 3)) { for (int item = 0; item < 14; item++) @@ -3391,6 +3410,7 @@ static void ShowDemoWindowTables() static bool display_width = false; static int contents_type = CT_Text; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&flags, ImGuiTableFlags_Borders); ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH"); @@ -3419,6 +3439,7 @@ static void ShowDemoWindowTables() ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); ImGui::Checkbox("Display headers", &display_headers); ImGui::Checkbox("Display debug width", &display_width); + PopStyleCompact(); if (ImGui::BeginTable("##table1", 3, flags)) { @@ -3477,9 +3498,11 @@ static void ShowDemoWindowTables() // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" // Each columns maintain a sizing weight, and they will occupy all available width. static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersV flag as well."); + PopStyleCompact(); if (ImGui::BeginTable("##table1", 3, flags)) { @@ -3530,9 +3553,8 @@ static void ShowDemoWindowTables() { HelpMarker("Using columns flag to alter resizing policy on a per-column basis."); static ImGuiTableFlags flags = ImGuiTableFlags_SizingPolicyFixedX | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; - //ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); // FIXME-TABLE: Explain or fix the effect of enable Scroll on outer_size - if (ImGui::BeginTable("##table1", 3, flags, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6))) + if (ImGui::BeginTable("##table1", 3, flags)) { ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed);// | ImGuiTableColumnFlags_NoResize); ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); @@ -3549,7 +3571,7 @@ static void ShowDemoWindowTables() } ImGui::EndTable(); } - if (ImGui::BeginTable("##table2", 6, flags, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6))) + if (ImGui::BeginTable("##table2", 6, flags)) { ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); @@ -3578,11 +3600,13 @@ static void ShowDemoWindowTables() { HelpMarker("Click and drag column headers to reorder columns.\n\nYou can also right-click on a header to open a context menu."); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBody); ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBodyUntilResize); + PopStyleCompact(); if (ImGui::BeginTable("##table1", 3, flags)) { @@ -3629,13 +3653,16 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Explicit widths")) { static ImGuiTableFlags flags = ImGuiTableFlags_None; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", (unsigned int*)&flags, ImGuiTableFlags_NoKeepColumnsVisible); + PopStyleCompact(); + if (ImGui::BeginTable("##table1", 3, flags)) { // We could also set ImGuiTableFlags_SizingPolicyFixedX on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 200.0f); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 30.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f); for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -3655,10 +3682,15 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Vertical scrolling, with clipping")) { HelpMarker("Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\nWe also demonstrate using ImGuiListClipper to virtualize the submission of many items."); - ImVec2 size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 7); static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; - ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + PopStyleCompact(); + + // When using ScrollX or ScrollY we need to specify a size for our table container! + // Otherwise by default the table will fit all available space, like a BeginChild() call. + ImVec2 size = ImVec2(0, TEXT_BASE_HEIGHT * 8); if (ImGui::BeginTable("##table1", 3, flags, size)) { ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible @@ -3690,16 +3722,21 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Horizontal scrolling")) { HelpMarker("When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingPolicyFixedX, as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\nAlso note that as of the current version, you will almost always want to enable ScrollY along with ScrollX, because the container window won't automatically extend vertically to fix contents (this may be improved in future versions)."); - ImVec2 size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 10); static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; static int freeze_cols = 1; static int freeze_rows = 1; + + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + PopStyleCompact(); + // When using ScrollX or ScrollY we need to specify a size for our table container! + // Otherwise by default the table will fit all available space, like a BeginChild() call. + ImVec2 size = ImVec2(0, TEXT_BASE_HEIGHT * 8); if (ImGui::BeginTable("##table1", 7, flags, size)) { ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); @@ -3741,13 +3778,10 @@ static void ShowDemoWindowTables() if (ImGui::BeginTable("##flags", column_count, ImGuiTableFlags_None)) { + PushStyleCompact(); for (int column = 0; column < column_count; column++) { - // Make the UI compact because there are so many fields ImGui::TableNextColumn(); - ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 2)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); ImGui::PushID(column); ImGui::AlignTextToFramePadding(); // FIXME-TABLE: Workaround for wrong text baseline propagation ImGui::Text("Flags for '%s'", column_names[column]); @@ -3765,8 +3799,8 @@ static void ShowDemoWindowTables() ImGui::CheckboxFlags("_IndentEnable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentEnable); ImGui::SameLine(); HelpMarker("Default for column 0"); ImGui::CheckboxFlags("_IndentDisable", (unsigned int*)&column_flags[column], ImGuiTableColumnFlags_IndentDisable); ImGui::SameLine(); HelpMarker("Default for column >0"); ImGui::PopID(); - ImGui::PopStyleVar(2); } + PopStyleCompact(); ImGui::EndTable(); } @@ -3777,9 +3811,10 @@ static void ShowDemoWindowTables() for (int column = 0; column < column_count; column++) ImGui::TableSetupColumn(column_names[column], column_flags[column]); ImGui::TableHeadersRow(); + float indent_step = (float)((int)TEXT_BASE_WIDTH / 2); for (int row = 0; row < 8; row++) { - ImGui::Indent(2.0f); // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags. + ImGui::Indent(indent_step); // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags. ImGui::TableNextRow(); for (int column = 0; column < column_count; column++) { @@ -3787,7 +3822,7 @@ static void ShowDemoWindowTables() ImGui::Text("%s %s", (column == 0) ? "Indented" : "Hello", ImGui::TableGetColumnName(column)); } } - ImGui::Unindent(2.0f * 8.0f); + ImGui::Unindent(indent_step * 8.0f); ImGui::EndTable(); } @@ -3809,7 +3844,7 @@ static void ShowDemoWindowTables() ImGui::TableNextColumn(); ImGui::Text("A0 Cell 0"); { - float rows_height = ImGui::GetTextLineHeightWithSpacing() * 2; + float rows_height = TEXT_BASE_HEIGHT * 2; if (ImGui::BeginTable("recurse2", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable)) { ImGui::TableSetupColumn("B0"); @@ -3844,11 +3879,12 @@ static void ShowDemoWindowTables() { HelpMarker("This section allows you to interact and see the effect of StretchX vs FixedX sizing policies depending on whether Scroll is enabled and the contents of your columns."); enum ContentsType { CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText }; - static int contents_type = CT_FillButton; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12); - ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); - static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg; + static int contents_type = CT_FillButton; + + PushStyleCompact(); + ImGui::SetNextItemWidth(TEXT_BASE_WIDTH * 22); + ImGui::Combo("Contents", &contents_type, "Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); @@ -3863,6 +3899,7 @@ static void ShowDemoWindowTables() ImGui::SameLine(); HelpMarker("Default if _ScrollX if enabled. Makes columns use _WidthFixed by default, or _WidthAlwaysAutoResize if _Resizable is not set."); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", (unsigned int*)&flags, ImGuiTableFlags_NoClip); + PopStyleCompact(); if (ImGui::BeginTable("##3ways", 3, flags, ImVec2(0, 100))) { @@ -3896,16 +3933,17 @@ static void ShowDemoWindowTables() { // FIXME-TABLE: Vertical border not overridden the same way as horizontal one HelpMarker("Setting style.CellPadding to (0,0)."); - static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; + static bool no_widget_frame = false; + + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", (unsigned int*)&flags, ImGuiTableFlags_BordersOuter); ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); - - static bool no_widget_frame = false; ImGui::Checkbox("no_widget_frame", &no_widget_frame); + PopStyleCompact(); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); if (ImGui::BeginTable("##3ways", 3, flags)) @@ -3942,7 +3980,7 @@ static void ShowDemoWindowTables() { for (int row = 0; row < 10; row++) { - float min_row_height = (float)(int)(ImGui::GetFontSize() * 0.30f * row); + float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row); ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); ImGui::TableNextColumn(); ImGui::Text("min_row_height = %.2f", min_row_height); @@ -3957,19 +3995,21 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Background color")) { static ImGuiTableFlags table_flags = ImGuiTableFlags_RowBg; - ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&table_flags, ImGuiTableFlags_Borders); - ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&table_flags, ImGuiTableFlags_RowBg); - ImGui::SameLine(); HelpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style."); - static int row_bg_type = 1; static int row_bg_target = 1; static int cell_bg_type = 1; + + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Borders", (unsigned int*)&table_flags, ImGuiTableFlags_Borders); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&table_flags, ImGuiTableFlags_RowBg); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style."); ImGui::Combo("row bg type", (int*)&row_bg_type, "None\0Red\0Gradient\0"); ImGui::Combo("row bg target", (int*)&row_bg_target, "RowBg0\0RowBg1\0"); ImGui::SameLine(); HelpMarker("Target RowBg0 to override the alternating odd/even colors,\nTarget RowBg1 to blend with them."); ImGui::Combo("cell bg type", (int*)&cell_bg_type, "None\0Blue\0"); ImGui::SameLine(); HelpMarker("We are colorizing cells to B1->C2 here."); IM_ASSERT(row_bg_type >= 0 && row_bg_type <= 2); IM_ASSERT(row_bg_target >= 0 && row_bg_target <= 1); IM_ASSERT(cell_bg_type >= 0 && cell_bg_type <= 1); + PopStyleCompact(); if (ImGui::BeginTable("##Table", 5, table_flags)) { @@ -4012,14 +4052,16 @@ static void ShowDemoWindowTables() if (ImGui::TreeNode("Tree view")) { static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; + //PushStyleCompact(); //ImGui::CheckboxFlags("ImGuiTableFlags_Scroll", (unsigned int*)&flags, ImGuiTableFlags_Scroll); + //PopStyleCompact(); if (ImGui::BeginTable("##3ways", 3, flags)) { // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); - ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 6); - ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 10); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 18.0f); ImGui::TableHeadersRow(); // Simple storage to output a dummy file-system. @@ -4133,7 +4175,10 @@ static void ShowDemoWindowTables() { HelpMarker("By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\nUsing ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body."); static ImGuiTableFlags flags1 = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_ContextMenuInBody; + + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", (unsigned int*)&flags1, ImGuiTableFlags_ContextMenuInBody); + PopStyleCompact(); // Context Menus: first example // [1.1] Right-click on the TableHeadersRow() line to open the default table context menu. @@ -4256,11 +4301,11 @@ static void ShowDemoWindowTables() } } - static ImGuiTableFlags flags = + ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_MultiSortable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable("##table", 4, flags, ImVec2(0, 250), 0.0f)) + if (ImGui::BeginTable("##table", 4, flags, ImVec2(0, TEXT_BASE_HEIGHT * 15), 0.0f)) { // Declare columns // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. @@ -4328,95 +4373,99 @@ static void ShowDemoWindowTables() static int freeze_cols = 1; static int freeze_rows = 1; static int items_count = IM_ARRAYSIZE(template_items_names); - static ImVec2 outer_size_value = ImVec2(0, 250); + static ImVec2 outer_size_value = ImVec2(0, TEXT_BASE_HEIGHT * 15); static float row_min_height = 0.0f; // Auto static float inner_width_with_scroll = 0.0f; // Auto-extend static bool outer_size_enabled = true; static bool show_headers = true; static bool show_wrapped_text = false; //static ImGuiTextFilter filter; - //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which affects sizing - if (ImGui::TreeNodeEx("Options")) + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which tend to affects column sizing + if (ImGui::TreeNode("Options")) { // Make the UI compact because there are so many fields - ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, 1)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 2)); - ImGui::PushItemWidth(200); + PushStyleCompact(); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 28.0f); - ImGui::BulletText("Features:"); - ImGui::Indent(); - ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); - ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); - ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); - ImGui::CheckboxFlags("ImGuiTableFlags_Sortable", (unsigned int*)&flags, ImGuiTableFlags_Sortable); - ImGui::CheckboxFlags("ImGuiTableFlags_MultiSortable", (unsigned int*)&flags, ImGuiTableFlags_MultiSortable); - ImGui::CheckboxFlags("ImGuiTableFlags_NoSavedSettings", (unsigned int*)&flags, ImGuiTableFlags_NoSavedSettings); - ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", (unsigned int*)&flags, ImGuiTableFlags_ContextMenuInBody); - ImGui::Unindent(); + if (ImGui::TreeNodeEx("Features:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", (unsigned int*)&flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", (unsigned int*)&flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", (unsigned int*)&flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_Sortable", (unsigned int*)&flags, ImGuiTableFlags_Sortable); + ImGui::CheckboxFlags("ImGuiTableFlags_MultiSortable", (unsigned int*)&flags, ImGuiTableFlags_MultiSortable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoSavedSettings", (unsigned int*)&flags, ImGuiTableFlags_NoSavedSettings); + ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", (unsigned int*)&flags, ImGuiTableFlags_ContextMenuInBody); + ImGui::TreePop(); + } - ImGui::BulletText("Decoration:"); - ImGui::Indent(); - ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); - ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); - ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); - ImGui::Unindent(); + if (ImGui::TreeNodeEx("Decoration:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", (unsigned int*)&flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", (unsigned int*)&flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", (unsigned int*)&flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", (unsigned int*)&flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", (unsigned int*)&flags, ImGuiTableFlags_BordersInnerH); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appears in Headers"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", (unsigned int*)&flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers)"); + ImGui::TreePop(); + } - ImGui::BulletText("Padding, Sizing:"); - ImGui::Indent(); - if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) - flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyStretchX); // Can't specify both sizing polices so we clear the other - ImGui::SameLine(); HelpMarker("[Default if ScrollX is off]\nFit all columns within available width (or specified inner_width). Fixed and Stretch columns allowed."); - if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyFixedX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyFixedX)) - flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyFixedX); // Can't specify both sizing polices so we clear the other - ImGui::SameLine(); HelpMarker("[Default if ScrollX is on]\nEnlarge as needed: enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rectangle. Only Fixed columns allowed. Stretched columns will calculate their width assuming no scrolling."); - ImGui::CheckboxFlags("ImGuiTableFlags_NoHeadersWidth", (unsigned int*)&flags, ImGuiTableFlags_NoHeadersWidth); - ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", (unsigned int*)&flags, ImGuiTableFlags_NoHostExtendY); - ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", (unsigned int*)&flags, ImGuiTableFlags_NoKeepColumnsVisible); - ImGui::SameLine(); HelpMarker("Only available if ScrollX is disabled."); - ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", (unsigned int*)&flags, ImGuiTableFlags_NoClip); - ImGui::SameLine(); HelpMarker("Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options."); - ImGui::Unindent(); + if (ImGui::TreeNodeEx("Sizing, Padding:", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyStretchX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyStretchX)) + flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyStretchX); // Can't specify both sizing polices so we clear the other + ImGui::SameLine(); HelpMarker("[Default if ScrollX is off]\nFit all columns within available width (or specified inner_width). Fixed and Stretch columns allowed."); + if (ImGui::CheckboxFlags("ImGuiTableFlags_SizingPolicyFixedX", (unsigned int*)&flags, ImGuiTableFlags_SizingPolicyFixedX)) + flags &= ~(ImGuiTableFlags_SizingPolicyMaskX_ ^ ImGuiTableFlags_SizingPolicyFixedX); // Can't specify both sizing polices so we clear the other + ImGui::SameLine(); HelpMarker("[Default if ScrollX is on]\nEnlarge as needed: enable scrollbar if ScrollX is enabled, otherwise extend parent window's contents rectangle. Only Fixed columns allowed. Stretched columns will calculate their width assuming no scrolling."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHeadersWidth", (unsigned int*)&flags, ImGuiTableFlags_NoHeadersWidth); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", (unsigned int*)&flags, ImGuiTableFlags_NoHostExtendY); + ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", (unsigned int*)&flags, ImGuiTableFlags_NoKeepColumnsVisible); + ImGui::SameLine(); HelpMarker("Only available if ScrollX is disabled."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", (unsigned int*)&flags, ImGuiTableFlags_NoClip); + ImGui::SameLine(); HelpMarker("Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options."); + ImGui::TreePop(); + } - ImGui::BulletText("Scrolling:"); - ImGui::Indent(); - ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); - ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); - ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); - ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); - ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); - ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + if (ImGui::TreeNodeEx("Scrolling:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", (unsigned int*)&flags, ImGuiTableFlags_ScrollX); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", (unsigned int*)&flags, ImGuiTableFlags_ScrollY); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::TreePop(); + } - ImGui::Unindent(); + if (ImGui::TreeNodeEx("Other:", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("show_headers", &show_headers); + ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); + ImGui::DragFloat2("##OuterSize", &outer_size_value.x); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Checkbox("outer_size", &outer_size_enabled); + ImGui::SameLine(); + HelpMarker("If scrolling is disabled (ScrollX and ScrollY not set), the table is output directly in the parent window. OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendV is set)."); - ImGui::BulletText("Other:"); - ImGui::Indent(); - ImGui::Checkbox("show_headers", &show_headers); - ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); - ImGui::DragFloat2("##OuterSize", &outer_size_value.x); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Checkbox("outer_size", &outer_size_enabled); - ImGui::SameLine(); - HelpMarker("If scrolling is disabled (ScrollX and ScrollY not set), the table is output directly in the parent window. OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendV is set)."); - - // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. - // To facilitate experimentation we expose two values and will select the right one depending on active flags. - ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); - ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); - ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); - ImGui::DragInt("items_count", &items_count, 0.1f, 0, 5000); - ImGui::Combo("contents_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); - //filter.Draw("filter"); - ImGui::Unindent(); + // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. + // To facilitate experimentation we expose two values and will select the right one depending on active flags. + ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); + ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); + ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); + ImGui::DragInt("items_count", &items_count, 0.1f, 0, 9999); + ImGui::Combo("contents_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); + //filter.Draw("filter"); + ImGui::TreePop(); + } ImGui::PopItemWidth(); - ImGui::PopStyleVar(2); + PopStyleCompact(); ImGui::Spacing(); ImGui::TreePop(); } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index c607a8b0..d7f99957 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -312,6 +312,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->RawData.resize(0); if (table->RawData.Size == 0) { + // For reference, the total _allocation count_ for a table is: + // + 0 (for ImGuiTable instance, we sharing allocation in g.Tables pool) + // + 1 (for table->RawData allocated below) + // + 1 (for table->Splitter._Channels) + // + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels) + // Where active_channels_count is variable but often == columns_count or columns_count + 1, see TableUpdateDrawChannels() for details. + // Unused channels don't perform their +2 allocations. + // Allocate single buffer for our arrays ImSpanAllocator<3> span_allocator; span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn)); @@ -957,7 +965,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) continue; - // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() + // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body; if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) continue; From ac5b1648e68fdb266b3b7c7591e627a4ecfd293a Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 20 Oct 2020 19:36:06 +0200 Subject: [PATCH 097/144] Tables: Various internal renaming + merge StartXHeaders/StartXRows into StartX. --- imgui.cpp | 26 +++++++++++----------- imgui.h | 10 ++++----- imgui_internal.h | 21 +++++++++--------- imgui_tables.cpp | 56 +++++++++++++++++++++++------------------------- 4 files changed, 55 insertions(+), 58 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4b85256d..fdabfd10 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10501,8 +10501,8 @@ void ImGui::ShowMetricsWindow(bool* p_open) // Debugging enums enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type const char* wrt_rects_names[WRT_Count] = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentRegionRect" }; - enum { TRT_OuterRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentRowsFrozen, TRT_ColumnsContentRowsUnfrozen, TRT_Count }; // Tables Rect Type - const char* trt_rects_names[TRT_Count] = { "OuterRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentRowsFrozen", "ColumnsContentRowsUnfrozen" }; + enum { TRT_OuterRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentFrozen, TRT_ColumnsContentUnfrozen, TRT_Count }; // Tables Rect Type + const char* trt_rects_names[TRT_Count] = { "OuterRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentFrozen", "ColumnsContentUnfrozen" }; if (cfg->ShowWindowsRectsType < 0) cfg->ShowWindowsRectsType = WRT_WorkRect; if (cfg->ShowTablesRectsType < 0) @@ -10512,17 +10512,17 @@ void ImGui::ShowMetricsWindow(bool* p_open) { static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n) { - if (rect_type == TRT_OuterRect) { return table->OuterRect; } - else if (rect_type == TRT_WorkRect) { return table->WorkRect; } - else if (rect_type == TRT_HostClipRect) { return table->HostClipRect; } - else if (rect_type == TRT_InnerClipRect) { return table->InnerClipRect; } - else if (rect_type == TRT_BackgroundClipRect) { return table->BackgroundClipRect; } - else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } - else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } - else if (rect_type == TRT_ColumnsContentHeadersUsed) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate - else if (rect_type == TRT_ColumnsContentHeadersIdeal) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " - else if (rect_type == TRT_ColumnsContentRowsFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthRowsFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // " - else if (rect_type == TRT_ColumnsContentRowsUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->MinX + c->ContentWidthRowsUnfrozen, table->InnerClipRect.Max.y); } // " + if (rect_type == TRT_OuterRect) { return table->OuterRect; } + else if (rect_type == TRT_WorkRect) { return table->WorkRect; } + else if (rect_type == TRT_HostClipRect) { return table->HostClipRect; } + else if (rect_type == TRT_InnerClipRect) { return table->InnerClipRect; } + else if (rect_type == TRT_BackgroundClipRect) { return table->BackgroundClipRect; } + else if (rect_type == TRT_ColumnsRect) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table->LastOuterHeight); } + else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; } + else if (rect_type == TRT_ColumnsContentHeadersUsed){ ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersUsed, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } // Note: y1/y2 not always accurate + else if (rect_type == TRT_ColumnsContentHeadersIdeal){ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } + else if (rect_type == TRT_ColumnsContentFrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MinX + c->ContentWidthFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); } + else if (rect_type == TRT_ColumnsContentUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->MinX + c->ContentWidthUnfrozen, table->InnerClipRect.Max.y); } IM_ASSERT(0); return ImRect(); } diff --git a/imgui.h b/imgui.h index 02879546..97ddc231 100644 --- a/imgui.h +++ b/imgui.h @@ -654,9 +654,9 @@ namespace ImGui // Tables // [ALPHA API] API may evolve! - // - Full-featured replacement for old Columns API + // - Full-featured replacement for old Columns API. // - See Demo->Tables for details. - // - See ImGuiTableFlags_ and ImGuiTableColumnsFlags_ enums for a description of available flags. + // - See ImGuiTableFlags_ and ImGuiTableColumnFlags_ enums for a description of available flags. // The typical call flow is: // - 1. Call BeginTable() // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults @@ -684,7 +684,7 @@ namespace ImGui IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true if column is visible. IMGUI_API int TableGetColumnIndex(); // return current column index. // Tables: Headers & Columns declaration - // - Use TableSetupColumn() to specify label, resizing policy, default width, id, various other flags etc. + // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc. // Important: this will not display anything! The name passed to TableSetupColumn() is used by TableHeadersRow() and context-menus. // - Use TableHeadersRow() to create a row and automatically submit a TableHeader() for each column. // Headers are required to perform some interactions: reordering, sorting, context menu (FIXME-TABLE: context menu should work without!) @@ -1070,13 +1070,13 @@ enum ImGuiTableFlags_ ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. ImGuiTableFlags_NoBordersInBody = 1 << 12, // Disable vertical borders in columns Body (borders will always appears in Headers). ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 13, // Disable vertical borders in columns Body until hovered for resize (borders will always appears in Headers). - // Sizing, Padding + // Sizing ImGuiTableFlags_SizingPolicyFixedX = 1 << 14, // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAlwaysAutoResize policy. Read description above for more details. ImGuiTableFlags_SizingPolicyStretchX = 1 << 15, // Default if ScrollX is off. Columns will default to use _WidthStretch policy. Read description above for more details. ImGuiTableFlags_NoHeadersWidth = 1 << 16, // Disable header width contribution to automatic width calculation. ImGuiTableFlags_NoHostExtendY = 1 << 17, // (FIXME-TABLE: Reword as SizingPolicy?) Disable extending past the limit set by outer_size.y, only meaningful when neither of ScrollX|ScrollY are set (data below the limit will be clipped and not visible) ImGuiTableFlags_NoKeepColumnsVisible = 1 << 18, // (FIXME-TABLE) Disable code that keeps column always minimally visible when table width gets too small and horizontal scrolling is off. - ImGuiTableFlags_NoClip = 1 << 19, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options. + ImGuiTableFlags_NoClip = 1 << 19, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze(). // Scrolling ImGuiTableFlags_ScrollX = 1 << 20, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Because this create a child window, ScrollY is currently generally recommended when using ScrollX. ImGuiTableFlags_ScrollY = 1 << 21, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. diff --git a/imgui_internal.h b/imgui_internal.h index ea45d384..dc81fdfa 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1901,15 +1901,14 @@ struct ImGuiTableColumn float WidthStretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from WidthStretchWeight in TableUpdateLayout() float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be >WidthRequest to honor minimum width, may be