Browse Source

Merge branch 'tables'

ocornut 4 years ago
parent
commit
9874077fc0
48 changed files with 6495 additions and 694 deletions
  1. 13 0
      docs/CHANGELOG.txt
  2. 1 0
      examples/example_allegro5/example_allegro5.vcxproj
  3. 3 0
      examples/example_allegro5/example_allegro5.vcxproj.filters
  4. 10 5
      examples/example_apple_metal/example_apple_metal.xcodeproj/project.pbxproj
  5. 4 0
      examples/example_apple_opengl2/example_apple_opengl2.xcodeproj/project.pbxproj
  6. 1 1
      examples/example_emscripten_opengl3/Makefile
  7. 1 1
      examples/example_glfw_metal/Makefile
  8. 1 1
      examples/example_glfw_opengl2/Makefile
  9. 1 0
      examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj
  10. 3 0
      examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters
  11. 1 1
      examples/example_glfw_opengl3/Makefile
  12. 1 0
      examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj
  13. 3 0
      examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters
  14. 1 1
      examples/example_glfw_vulkan/CMakeLists.txt
  15. 1 0
      examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj
  16. 3 0
      examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters
  17. 1 1
      examples/example_glut_opengl2/Makefile
  18. 1 0
      examples/example_glut_opengl2/example_glut_opengl2.vcxproj
  19. 3 0
      examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters
  20. 1 0
      examples/example_marmalade/marmalade_example.mkb
  21. 1 1
      examples/example_null/Makefile
  22. 1 0
      examples/example_sdl_directx11/example_sdl_directx11.vcxproj
  23. 3 0
      examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters
  24. 1 1
      examples/example_sdl_metal/Makefile
  25. 1 1
      examples/example_sdl_opengl2/Makefile
  26. 1 0
      examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj
  27. 3 0
      examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters
  28. 1 1
      examples/example_sdl_opengl3/Makefile
  29. 2 1
      examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj
  30. 3 0
      examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters
  31. 2 1
      examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj
  32. 2 1
      examples/example_win32_directx10/example_win32_directx10.vcxproj
  33. 4 1
      examples/example_win32_directx10/example_win32_directx10.vcxproj.filters
  34. 1 0
      examples/example_win32_directx11/example_win32_directx11.vcxproj
  35. 3 0
      examples/example_win32_directx11/example_win32_directx11.vcxproj.filters
  36. 2 1
      examples/example_win32_directx12/example_win32_directx12.vcxproj
  37. 4 1
      examples/example_win32_directx12/example_win32_directx12.vcxproj.filters
  38. 2 1
      examples/example_win32_directx9/example_win32_directx9.vcxproj
  39. 4 1
      examples/example_win32_directx9/example_win32_directx9.vcxproj.filters
  40. 163 23
      imgui.cpp
  41. 232 5
      imgui.h
  42. 1900 195
      imgui_demo.cpp
  43. 15 0
      imgui_draw.cpp
  44. 355 1
      imgui_internal.h
  45. 3709 0
      imgui_tables.cpp
  46. 15 447
      imgui_widgets.cpp
  47. 10 0
      misc/natvis/imgui.natvis
  48. 1 0
      misc/single_file/imgui_single_file.h

+ 13 - 0
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
@@ -58,6 +59,18 @@ Breaking Changes:
 
 Other Changes:
 
+- Tables: added new Tables Beta API as a replacement for old Columns. (#2957, #125)
+  Check out 'Demo->Tables' for many demos + API comments in imgui.h for details.
+  - Added 16 functions: BeginTable(), EndTable(), 
+                        TableNextRow(), TableNextColumn(), TableSetColumnIndex(), TableGetColumnIndex(), TableGetRowIndex(), 
+                        TableSetupColumn(), TableSetupScrollFreeze(),
+                        TableHeadersRow(), TableHeader(), TableSetBgColor(), TableGetSortSpecs(),
+                        TableGetColumnCount(), TableGetColumnName(), TableGetColumnFlags().
+  - Added 2 structures: ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs.
+  - Added 2 enums:      ImGuiSortDirection, ImGuiTableBgTarget.
+  - Added 3 flags sets: ImGuiTableFlags (27 flags), ImGuiTableColumnFlags (24 flags), ImGuiTableRowFlags (1 flags).
+  - Added 5 colors:     ImGuiCol_TableHeaderBg, ImGuiCol_TableBorderStrong, ImGuiCol_TableBorderLight, ImGuiCol_TableRowBg, ImGuiCol_TableRowBgAlt.
+  - Added 1 style var:  ImGuiStyleVar_CellPadding.
 - Tab Bar: Made it possible to append to an existing tab bar by calling BeginTabBar()/EndTabBar() again.
 - Tab Bar: Fixed using more than 128 tabs in a tab bar (scrolling policy recommended).
 - Tab Bar: Do not display a tooltip if the name already fits over a given tab. (#3521)

+ 1 - 0
examples/example_allegro5/example_allegro5.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_allegro5.cpp" />
     <ClCompile Include="imconfig_allegro5.h" />

+ 3 - 0
examples/example_allegro5/example_allegro5.vcxproj.filters

@@ -28,6 +28,9 @@
     <ClCompile Include="imconfig_allegro5.h">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 10 - 5
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 = "<group>"; };
 		07A82ED72139413C0078D120 /* imgui_widgets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_widgets.cpp; path = ../../imgui_widgets.cpp; sourceTree = "<group>"; };
+		5079822D257677DB0038A28D /* imgui_tables.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui_tables.cpp; path = ../../imgui_tables.cpp; sourceTree = "<group>"; };
 		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;

+ 4 - 0
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 = "<group>"; };
 		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 = "<group>"; };
 /* 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;

+ 1 - 1
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)

+ 1 - 1
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))))
 

+ 1 - 1
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)

+ 1 - 0
examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_glfw.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_opengl2.cpp" />

+ 3 - 0
examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj.filters

@@ -22,6 +22,9 @@
     <ClCompile Include="..\..\imgui_draw.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 1 - 1
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)

+ 1 - 0
examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_glfw.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_opengl3.cpp" />

+ 3 - 0
examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj.filters

@@ -28,6 +28,9 @@
     <ClCompile Include="..\..\imgui_draw.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 1 - 1
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})

+ 1 - 0
examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_glfw.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_vulkan.cpp" />

+ 3 - 0
examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj.filters

@@ -22,6 +22,9 @@
     <ClCompile Include="..\..\imgui_draw.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 1 - 1
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)

+ 1 - 0
examples/example_glut_opengl2/example_glut_opengl2.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_glut.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_opengl2.cpp" />

+ 3 - 0
examples/example_glut_opengl2/example_glut_opengl2.vcxproj.filters

@@ -22,6 +22,9 @@
     <ClCompile Include="..\..\imgui_draw.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 1 - 0
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

+ 1 - 1
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)
 

+ 1 - 0
examples/example_sdl_directx11/example_sdl_directx11.vcxproj

@@ -159,6 +159,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_dx11.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_sdl.cpp" />

+ 3 - 0
examples/example_sdl_directx11/example_sdl_directx11.vcxproj.filters

@@ -38,6 +38,9 @@
     <ClCompile Include="..\..\imgui_draw.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 1 - 1
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))))
 

+ 1 - 1
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)

+ 1 - 0
examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_opengl2.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_sdl.cpp" />

+ 3 - 0
examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj.filters

@@ -22,6 +22,9 @@
     <ClCompile Include="main.cpp">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 1 - 1
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)

+ 2 - 1
examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_opengl3.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_sdl.cpp" />
@@ -180,4 +181,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 3 - 0
examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj.filters

@@ -34,6 +34,9 @@
     <ClCompile Include="..\..\backends\imgui_impl_sdl.cpp">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>

+ 2 - 1
examples/example_sdl_vulkan/example_sdl_vulkan.vcxproj

@@ -158,6 +158,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_sdl.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_vulkan.cpp" />
@@ -177,4 +178,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 2 - 1
examples/example_win32_directx10/example_win32_directx10.vcxproj

@@ -155,6 +155,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_dx10.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_win32.cpp" />
@@ -167,4 +168,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 4 - 1
examples/example_win32_directx10/example_win32_directx10.vcxproj.filters

@@ -44,6 +44,9 @@
     <ClCompile Include="..\..\backends\imgui_impl_win32.cpp">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
@@ -54,4 +57,4 @@
       <Filter>sources</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>

+ 1 - 0
examples/example_win32_directx11/example_win32_directx11.vcxproj

@@ -154,6 +154,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_dx11.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_win32.cpp" />

+ 3 - 0
examples/example_win32_directx11/example_win32_directx11.vcxproj.filters

@@ -47,6 +47,9 @@
     <ClCompile Include="..\..\backends\imgui_impl_dx11.cpp">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <None Include="..\README.txt" />

+ 2 - 1
examples/example_win32_directx12/example_win32_directx12.vcxproj

@@ -157,6 +157,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_dx12.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_win32.cpp" />
@@ -168,4 +169,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 4 - 1
examples/example_win32_directx12/example_win32_directx12.vcxproj.filters

@@ -44,6 +44,9 @@
     <ClCompile Include="..\..\backends\imgui_impl_win32.cpp">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
@@ -51,4 +54,4 @@
   <ItemGroup>
     <None Include="..\README.txt" />
   </ItemGroup>
-</Project>
+</Project>

+ 2 - 1
examples/example_win32_directx9/example_win32_directx9.vcxproj

@@ -148,6 +148,7 @@
     <ClCompile Include="..\..\imgui.cpp" />
     <ClCompile Include="..\..\imgui_demo.cpp" />
     <ClCompile Include="..\..\imgui_draw.cpp" />
+    <ClCompile Include="..\..\imgui_tables.cpp" />
     <ClCompile Include="..\..\imgui_widgets.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_dx9.cpp" />
     <ClCompile Include="..\..\backends\imgui_impl_win32.cpp" />
@@ -167,4 +168,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
+</Project>

+ 4 - 1
examples/example_win32_directx9/example_win32_directx9.vcxproj.filters

@@ -28,6 +28,9 @@
     <ClCompile Include="..\..\backends\imgui_impl_dx9.cpp">
       <Filter>sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\imgui_tables.cpp">
+      <Filter>imgui</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\imgui_widgets.cpp">
       <Filter>imgui</Filter>
     </ClCompile>
@@ -55,4 +58,4 @@
       <Filter>sources</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>

+ 163 - 23
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/.
@@ -946,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).
@@ -986,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);
@@ -2134,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->HostSkipItems : 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
@@ -2148,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;
@@ -2184,12 +2195,22 @@ 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
+    if (ImGuiTable* table = g.CurrentTable)
+    {
+        if (table->IsInsideRow)
+            ImGui::TableEndRow(table);
+        table->RowPosY2 = window->DC.CursorPos.y;
+        const int row_increase = (int)((off_y / line_height) + 0.5f);
+        //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow()
+        table->RowBgColorCounter += row_increase;
+    }
 }
 
 ImGuiListClipper::ImGuiListClipper()
@@ -2211,9 +2232,14 @@ 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;
+    ItemsFrozen = 0;
     StepNo = 0;
     DisplayStart = -1;
     DisplayEnd = 0;
@@ -2226,7 +2252,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;
 }
@@ -2236,8 +2262,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;
@@ -2246,12 +2276,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->IsUnfrozen)
+        {
+            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;
         }
@@ -2265,7 +2305,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;
     }
@@ -2282,7 +2332,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;
@@ -2294,7 +2344,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;
     }
@@ -2404,6 +2454,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
@@ -2511,6 +2562,11 @@ 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_TableBorderStrong: return "TableBorderStrong";
+    case ImGuiCol_TableBorderLight: return "TableBorderLight";
+    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";
@@ -2888,6 +2944,7 @@ static void SetCurrentWindow(ImGuiWindow* window)
 {
     ImGuiContext& g = *GImGui;
     g.CurrentWindow = window;
+    g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL;
     if (window)
         g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
 }
@@ -2897,6 +2954,7 @@ void ImGui::GcCompactTransientMiscBuffers()
     ImGuiContext& g = *GImGui;
     g.ItemFlagsStack.clear();
     g.GroupStack.clear();
+    TableGcCompactSettings();
 }
 
 // Free up/compact internal window buffers, we can use this when a window becomes unused.
@@ -3851,6 +3909,11 @@ void ImGui::NewFrame()
         if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
             GcCompactTransientWindowBuffers(window);
     }
+
+    // Garbage collect transient buffers of recently unused tables
+    for (int i = 0; i < g.TablesLastTimeActive.Size; i++)
+        if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
+            TableGcCompactTransientBuffers(g.Tables.GetByIndex(i));
     if (g.GcCompactAll)
         GcCompactTransientMiscBuffers();
     g.GcCompactAll = false;
@@ -3927,15 +3990,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);
-    }
+    TableSettingsInstallHandler(context);
 #endif // #ifdef IMGUI_HAS_TABLE
 
 #ifdef IMGUI_HAS_DOCK
@@ -3997,6 +4052,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();
@@ -5583,6 +5642,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)
@@ -7373,7 +7433,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;
 }
@@ -7384,7 +7444,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;
 }
@@ -10449,8 +10509,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)
@@ -10458,6 +10518,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->BgClipRect; }
+            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->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed, 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->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal, table->InnerClipRect.Min.y + table->LastFirstRowHeight); }
+            else if (rect_type == TRT_ColumnsContentFrozen)     { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table->LastFirstRowHeight); }
+            else if (rect_type == TRT_ColumnsContentUnfrozen)   { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight, c->ContentMaxXUnfrozen, 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(); }
@@ -10499,6 +10576,52 @@ 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 && table->InnerWindow != 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();
+                char buf[128];
+                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);
+                            ImFormatString(buf, IM_ARRAYSIZE(buf), "(%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]);
+                            Selectable(buf);
+                            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);
+                        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%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]);
+                        Selectable(buf);
+                        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();
     }
 
@@ -10532,7 +10655,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()))
     {
@@ -10663,11 +10785,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

+ 232 - 5
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 ImGuiTableSortSpecs and ImGuiTableColumnSortSpecs 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 ImGuiTableColumnSortSpecs;   // 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,7 +155,9 @@ 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 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
@@ -170,6 +176,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()
 
@@ -643,11 +652,67 @@ 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
+    // [BETA API] API may evolve!
+    // - Full-featured replacement for old Columns API.
+    // - See Demo->Tables for details.
+    // - 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
+    // - 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)
+    // - 5. Populate contents
+    //    - In most situations you can use TableNextRow() + TableSetColumnIndex(N) 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!
+    //    - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing
+    //      width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know
+    //      it is not going to contribute to row height.
+    //      In many situations, you may skip submitting contents for every columns but one (e.g. the first one).
+    //    - 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          TableNextColumn();                          // append into the next column (or first column of next row if currently in last column). Return true when column is visible.
+    IMGUI_API bool          TableSetColumnIndex(int column_n);          // append into the specified column. Return true when column is visible.
+    IMGUI_API int           TableGetColumnIndex();                      // return current column index.
+    IMGUI_API int           TableGetRowIndex();                         // return current row index.
+    // Tables: Headers & Columns declaration
+    // - 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: reordering, sorting, and opening the context menu (but context menu can also be available in columns body using ImGuiTableFlags_ContextMenuInBody).
+    // - 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).
+    // - 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
+    // - 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 int                   TableGetColumnCount();                      // return number of columns (value passed to BeginTable)
+    IMGUI_API const char*           TableGetColumnName(int column_n = -1);      // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column.
+    IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1);     // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags.
+    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.
+
+    // Legacy Columns API (2020: 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
@@ -967,6 +1032,127 @@ 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()
+// - 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_ColumnsWidthStretch -> all Columns defaults to ImGuiTableColumnFlags_WidthStretch.
+//    - Columns sizing policy allowed: Stretch (default) or Fixed/Auto.
+//    - 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_ColumnsWidthFixed -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed.
+//    - Columns sizing policy allowed: Fixed/Auto mostly! 
+//    - Fixed Columns can be enlarged as needed. Table will show an horizontal scrollbar if needed.
+//    - Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable().
+// - 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
+    ImGuiTableFlags_None                            = 0,
+    ImGuiTableFlags_Resizable                       = 1 << 0,   // Allow resizing columns.
+    ImGuiTableFlags_Reorderable                     = 1 << 1,   // Allow reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers)
+    ImGuiTableFlags_Hideable                        = 1 << 2,   // Allow hiding/disabling columns in context menu.
+    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().
+    // Decorations
+    ImGuiTableFlags_RowBg                           = 1 << 7,   // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of 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_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
+    ImGuiTableFlags_ColumnsWidthStretch             = 1 << 14,  // Default if ScrollX is off. Columns will default to use _WidthStretch. Read description above for more details.
+    ImGuiTableFlags_ColumnsWidthFixed               = 1 << 15,  // Default if ScrollX is on. Columns will default to use _WidthFixed or _WidthAutoResize policy (if Resizable is off). Read description above for more details.
+    ImGuiTableFlags_SameWidths                      = 1 << 16,  // Make all columns the same widths which is useful with Fixed columns policy (but granted by default with Stretch policy + no resize). Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible and disable ImGuiTableFlags_Resizable.
+    ImGuiTableFlags_NoHeadersWidth                  = 1 << 17,  // Disable headers' contribution to automatic width calculation.
+    ImGuiTableFlags_NoHostExtendY                   = 1 << 18,  // 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,  // Disable keeping column always minimally visible when ScrollX is off and table gets too small.
+    ImGuiTableFlags_PreciseWidths                   = 1 << 20,  // Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.
+    ImGuiTableFlags_NoClip                          = 1 << 21,  // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze().
+    // Padding
+    ImGuiTableFlags_PadOuterX                       = 1 << 22,  // Default if BordersOuterV is on. Enable outer-most padding.
+    ImGuiTableFlags_NoPadOuterX                     = 1 << 23,  // Default if BordersOuterV is off. Disable outer-most padding.
+    ImGuiTableFlags_NoPadInnerX                     = 1 << 24,  // Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off).
+    // Scrolling
+    ImGuiTableFlags_ScrollX                         = 1 << 25,  // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this create a child window, ScrollY is currently generally recommended when using ScrollX.
+    ImGuiTableFlags_ScrollY                         = 1 << 26   // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size.
+};
+
+// Flags for ImGui::TableSetupColumn()
+enum ImGuiTableColumnFlags_
+{
+    // Input configuration flags
+    ImGuiTableColumnFlags_None                      = 0,
+    ImGuiTableColumnFlags_DefaultHide               = 1 << 0,   // Default as a hidden/disabled column.
+    ImGuiTableColumnFlags_DefaultSort               = 1 << 1,   // Default as a sorting column.
+    ImGuiTableColumnFlags_WidthStretch              = 1 << 2,   // Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _ColumnsWidthStretch).
+    ImGuiTableColumnFlags_WidthFixed                = 1 << 3,   // Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _ColumnsWidthFixed and table is resizable).
+    ImGuiTableColumnFlags_WidthAutoResize           = 1 << 4,   // Column will not stretch and keep resizing based on submitted contents (default if table sizing policy is _ColumnsWidthFixed and table is not resizable).
+    ImGuiTableColumnFlags_NoResize                  = 1 << 5,   // Disable manual resizing.
+    ImGuiTableColumnFlags_NoReorder                 = 1 << 6,   // Disable manual reordering this column, this will also prevent other columns from crossing over this column.
+    ImGuiTableColumnFlags_NoHide                    = 1 << 7,   // Disable ability to hide/disable this column.
+    ImGuiTableColumnFlags_NoClip                    = 1 << 8,   // Disable clipping for this column (all NoClip columns will render in a same draw command).
+    ImGuiTableColumnFlags_NoSort                    = 1 << 9,   // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table).
+    ImGuiTableColumnFlags_NoSortAscending           = 1 << 10,  // Disable ability to sort in the ascending direction.
+    ImGuiTableColumnFlags_NoSortDescending          = 1 << 11,  // Disable ability to sort in the descending direction.
+    ImGuiTableColumnFlags_NoHeaderWidth             = 1 << 12,  // Header width don't contribute to automatic column width.
+    ImGuiTableColumnFlags_PreferSortAscending       = 1 << 13,  // Make the initial sort direction Ascending when first sorting on this column (default).
+    ImGuiTableColumnFlags_PreferSortDescending      = 1 << 14,  // Make the initial sort direction Descending when first sorting on this column.
+    ImGuiTableColumnFlags_IndentEnable              = 1 << 15,  // Use current Indent value when entering cell (default for column 0).
+    ImGuiTableColumnFlags_IndentDisable             = 1 << 16,  // Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored.
+
+    // Output status flags, read-only via TableGetColumnFlags()
+    ImGuiTableColumnFlags_IsEnabled                 = 1 << 20,  // Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags.
+    ImGuiTableColumnFlags_IsVisible                 = 1 << 21,  // Status: is visible == is enabled AND not clipped by scrolling.
+    ImGuiTableColumnFlags_IsSorted                  = 1 << 22,  // Status: is currently part of the sort specs
+    ImGuiTableColumnFlags_IsHovered                 = 1 << 23,  // Status: is hovered by mouse
+
+    // [Internal] Combinations and masks
+    ImGuiTableColumnFlags_WidthMask_                = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAutoResize,
+    ImGuiTableColumnFlags_IndentMask_               = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable,
+    ImGuiTableColumnFlags_StatusMask_               = ImGuiTableColumnFlags_IsEnabled | ImGuiTableColumnFlags_IsVisible | ImGuiTableColumnFlags_IsSorted | ImGuiTableColumnFlags_IsHovered,
+    ImGuiTableColumnFlags_NoDirectResize_           = 1 << 30   // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge)
+};
+
+// 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)
+};
+
+// 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_
 {
@@ -1044,6 +1230,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 +1382,11 @@ enum ImGuiCol_
     ImGuiCol_PlotLinesHovered,
     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,
     ImGuiCol_DragDropTarget,
     ImGuiCol_NavHighlight,          // Gamepad/keyboard: current highlighted item
@@ -1228,6 +1427,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 +1664,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 +1901,30 @@ struct ImGuiPayload
     bool IsDelivery() const                 { return Delivery; }
 };
 
+// Sorting specification for one column of a table (sizeof == 12 bytes)
+struct ImGuiTableColumnSortSpecs
+{
+    ImGuiID                     ColumnUserID;       // User id of the column (if specified by a TableSetupColumn() call)
+    ImS16                       ColumnIndex;        // Index of the column
+    ImS16                       SortOrder;          // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here)
+    ImGuiSortDirection          SortDirection : 8;  // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more convenient for your sort function)
+
+    ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); }
+};
+
+// Sorting specifications for a table (often handling sort specs for a single column, occasionally more)
+// 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 ImGuiTableColumnSortSpecs* 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.
+
+    ImGuiTableSortSpecs()       { memset(this, 0, sizeof(*this)); }
+};
+
 //-----------------------------------------------------------------------------
 // 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.
@@ -1888,6 +2113,7 @@ struct ImGuiListClipper
     // [Internal]
     int     ItemsCount;
     int     StepNo;
+    int     ItemsFrozen;
     float   ItemsHeight;
     float   StartPosY;
 
@@ -2028,8 +2254,9 @@ struct ImDrawChannel
     ImVector<ImDrawIdx>         _IdxBuffer;
 };
 
+
 // Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order.
-// This is used by the Columns api, so items of each column can be batched together in a same draw call.
+// This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call.
 struct ImDrawListSplitter
 {
     int                         _Current;    // Current channel number (0)

File diff suppressed because it is too large
+ 1900 - 195
imgui_demo.cpp


+ 15 - 0
imgui_draw.cpp

@@ -222,6 +222,11 @@ 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_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.06f);
     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 +282,11 @@ 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_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);
     colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
     colors[ImGuiCol_NavHighlight]           = colors[ImGuiCol_HeaderHovered];
@@ -333,6 +343,11 @@ 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_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.09f);
     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];

+ 355 - 1
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)
@@ -245,6 +249,7 @@ namespace ImStb
 // - Helper: ImRect
 // - Helper: ImBitArray
 // - Helper: ImBitVector
+// - Helper: ImSpan<>, ImSpanAllocator<>
 // - Helper: ImPool<>
 // - Helper: ImChunkStream<>
 //-----------------------------------------------------------------------------
@@ -264,6 +269,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
@@ -471,6 +477,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<int BITCOUNT>
+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
@@ -483,6 +503,55 @@ 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<typename T>
+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 int          size_in_bytes() const       { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); }
+    inline T&           operator[](int i)           { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; }
+    inline const T&     operator[](int i) const     { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; }
+
+    inline T*           begin()                     { return Data; }
+    inline const T*     begin() const               { return Data; }
+    inline T*           end()                       { return DataEnd; }
+    inline const T*     end() const                 { return DataEnd; }
+
+    // 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: ImSpanAllocator<>
+// Facilitate storing multiple chunks into a single large block (the "arena")
+template<int CHUNKS>
+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); 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]); }
+    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<typename T>
+    inline void  GetSpan(int n, ImSpan<T>* 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.
@@ -529,6 +598,8 @@ struct IMGUI_API ImChunkStream
     T*      end()                       { return (T*)(void*)(Buf.Data + Buf.Size); }
     int     offset_from_ptr(const T* p) { IM_ASSERT(p >= begin() && p < end()); const ptrdiff_t off = (const char*)p - Buf.Data; return (int)off; }
     T*      ptr_from_offset(int off)    { IM_ASSERT(off >= 4 && off < Buf.Size); return (T*)(void*)(Buf.Data + off); }
+    void    swap(ImChunkStream<T>& rhs) { rhs.Buf.swap(Buf); }
+
 };
 
 //-----------------------------------------------------------------------------
@@ -1329,6 +1400,13 @@ struct ImGuiContext
     ImVector<unsigned char> 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<ImGuiTable>              Tables;
+    ImVector<ImGuiPtrOrIndex>       CurrentTableStack;
+    ImVector<float>                 TablesLastTimeActive;       // Last used timestamp of each tables (SOA, for efficient GC)
+    ImVector<ImDrawChannel>         DrawChannelsTempMergeBuffer;
+
     // Tab bars
     ImGuiTabBar*                    CurrentTabBar;
     ImPool<ImGuiTabBar>             TabBars;
@@ -1352,6 +1430,7 @@ struct ImGuiContext
     float                   DragSpeedDefaultRatio;              // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio
     float                   ScrollbarClickDeltaToGrabCenter;    // Distance between mouse and center of grab box, normalized in parent space. Use storage?
     int                     TooltipOverrideCount;
+    float                   TooltipSlowDelay;                   // Time before slow tooltips appears (FIXME: This is temporary until we merge in tooltip timer+priority work)
     ImVector<char>          ClipboardHandlerData;               // If no custom clipboard handler is defined
     ImVector<ImGuiID>       MenusIdSubmittedThisFrame;          // A list of menu IDs that were rendered at least once
 
@@ -1366,6 +1445,7 @@ struct ImGuiContext
     ImGuiTextBuffer         SettingsIniData;                    // In memory .ini settings
     ImVector<ImGuiSettingsHandler>      SettingsHandlers;       // List of .ini settings handlers
     ImChunkStream<ImGuiWindowSettings>  SettingsWindows;        // ImGuiWindow .ini settings entries
+    ImChunkStream<ImGuiTableSettings>   SettingsTables;         // ImGuiTable .ini settings entries
     ImVector<ImGuiContextHook>          Hooks;                  // Hooks for extensions (e.g. test engine)
 
     // Capture/Logging
@@ -1496,6 +1576,7 @@ struct ImGuiContext
         DragDropHoldJustPressedId = 0;
         memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal));
 
+        CurrentTable = NULL;
         CurrentTabBar = NULL;
 
         LastValidMousePos = ImVec2(0.0f, 0.0f);
@@ -1510,6 +1591,7 @@ struct ImGuiContext
         DragSpeedDefaultRatio = 1.0f / 100.0f;
         ScrollbarClickDeltaToGrabCenter = 0.0f;
         TooltipOverrideCount = 0;
+        TooltipSlowDelay = 0.50f;
 
         PlatformImePos = PlatformImeLastPos = ImVec2(FLT_MAX, FLT_MAX);
         PlatformLocaleDecimalPoint = '.';
@@ -1581,6 +1663,7 @@ struct IMGUI_API ImGuiWindowTempData
     ImVector<ImGuiWindow*>  ChildWindows;
     ImGuiStorage*           StateStorage;           // Current persistent per-window storage (store e.g. tree node open/close state)
     ImGuiOldColumns*        CurrentColumns;         // Current columns 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)
@@ -1805,7 +1888,230 @@ struct ImGuiTabBar
 //-----------------------------------------------------------------------------
 
 #ifdef IMGUI_HAS_TABLE
-// <this is filled in 'tables' branch>
+
+#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   (4 + 64 * 2)        // See TableSetupDrawChannels()
+
+// Our current column maximum is 64 but we may raise that in the future.
+typedef ImS8 ImGuiTableColumnIdx;
+typedef ImU8 ImGuiTableDrawChannelIdx;
+
+// [Internal] sizeof() ~ 104
+// We use the terminology "Enabled" to refer to a column that is not Hidden by user/api.
+// We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping. 
+// This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped".
+struct ImGuiTableColumn
+{
+    ImRect                  ClipRect;                       // Clipping rectangle for the column
+    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                   MinX;                           // Absolute positions
+    float                   MaxX;
+    float                   InitStretchWeightOrWidth;       // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_).
+    float                   StretchWeight;                  // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially.
+    float                   WidthAuto;                      // Automatic width
+    float                   WidthRequest;                   // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout()
+    float                   WidthGiven;                     // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space.
+    float                   WorkMinX;                       // Start position for the frame, currently ~(MinX + CellPaddingX)
+    float                   WorkMaxX;
+    float                   ItemWidth;
+    float                   ContentMaxXFrozen;              // Contents maximum position for frozen rows (apart from headers), from which we can infer content width.
+    float                   ContentMaxXUnfrozen;
+    float                   ContentMaxXHeadersUsed;         // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls
+    float                   ContentMaxXHeadersIdeal;
+    ImS16                   NameOffset;                     // Offset into parent ColumnsNames[]
+    ImGuiTableColumnIdx     DisplayOrder;                   // Index within Table's IndexToDisplayOrder[] (column may be reordered by users)
+    ImGuiTableColumnIdx     IndexWithinEnabledSet;          // Index within enabled/visible set (<= IndexToDisplayOrder)
+    ImGuiTableColumnIdx     PrevEnabledColumn;              // Index of prev enabled/visible column within Columns[], -1 if first enabled/visible column
+    ImGuiTableColumnIdx     NextEnabledColumn;              // Index of next enabled/visible column within Columns[], -1 if last enabled/visible column
+    ImGuiTableColumnIdx     SortOrder;                      // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort
+    ImGuiTableDrawChannelIdx DrawChannelCurrent;            // Index within DrawSplitter.Channels[]
+    ImGuiTableDrawChannelIdx DrawChannelFrozen;
+    ImGuiTableDrawChannelIdx DrawChannelUnfrozen;
+    bool                    IsEnabled;                      // Is the column not marked Hidden by the user? (even if off view, e.g. clipped by scrolling).
+    bool                    IsEnabledNextFrame;
+    bool                    IsVisibleX;                     // Is actually in view (e.g. overlapping the host window clipping rectangle, not scrolled).
+    bool                    IsVisibleY;
+    bool                    IsRequestOutput;                // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not.
+    bool                    IsSkipItems;                    // Do we want item submissions to this column to be completely ignored (no layout will happen).
+    bool                    IsPreserveWidthAuto;
+    ImS8                    NavLayerCurrent;                // ImGuiNavLayer in 1 byte
+    ImS8                    SortDirection;                  // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending
+    ImU8                    AutoFitQueue;                   // Queue of 8 values for the next 8 frames to request auto-fit
+    ImU8                    CannotSkipItemsQueue;           // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem
+
+    ImGuiTableColumn()
+    {
+        memset(this, 0, sizeof(*this));
+        StretchWeight = WidthRequest = -1.0f;
+        NameOffset = -1;
+        DisplayOrder = IndexWithinEnabledSet = -1;
+        PrevEnabledColumn = NextEnabledColumn = -1;
+        SortOrder = -1;
+        SortDirection = ImGuiSortDirection_None;
+        DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1;
+    }
+};
+
+// Transient cell data stored per row.
+// sizeof() ~ 6
+struct ImGuiTableCellData
+{
+    ImU32                       BgColor;    // Actual color
+    ImGuiTableColumnIdx         Column;     // Column number
+};
+
+// FIXME-TABLES: transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData
+struct ImGuiTable
+{
+    ImGuiID                     ID;
+    ImGuiTableFlags             Flags;
+    void*                       RawData;                    // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[]
+    ImSpan<ImGuiTableColumn>    Columns;                    // Point within RawData[]
+    ImSpan<ImGuiTableColumnIdx> DisplayOrderToIndex;        // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1)
+    ImSpan<ImGuiTableCellData>  RowCellData;                // Point within RawData[]. Store cells background requests for current row.
+    ImU64                       EnabledMaskByDisplayOrder;  // Column DisplayOrder -> IsEnabled map
+    ImU64                       EnabledMaskByIndex;         // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data
+    ImU64                       VisibleMaskByIndex;         // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect)
+    ImU64                       RequestOutputMaskByIndex;   // Column Index -> IsVisible || AutoFit (== expect user to submit items)
+    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                         CurrentRow;
+    int                         CurrentColumn;
+    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;
+    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), not same as CurrentRow because header rows typically don't increase this.
+    ImU32                       RowBgColor[2];              // Background color override for current row.
+    ImU32                       BorderColorStrong;
+    ImU32                       BorderColorLight;
+    float                       BorderX1;
+    float                       BorderX2;
+    float                       HostIndentX;
+    float                       OuterPaddingX;
+    float                       CellPaddingX;               // Padding from each borders
+    float                       CellPaddingY;
+    float                       CellSpacingX1;              // Spacing between non-bordered cells
+    float                       CellSpacingX2;
+    float                       LastOuterHeight;            // Outer height from last frame
+    float                       LastFirstRowHeight;         // Height of first row from last frame
+    float                       InnerWidth;                 // User value passed to BeginTable(), see comments at the top of BeginTable() for details.
+    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;
+    ImRect                      BgClipRect;                 // We use this to cpu-clip cell background color fill
+    ImRect                      BgClipRectForDrawCmd;
+    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                      HostBackupWorkRect;         // 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                      HostBackupPrevLineSize;     // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable()
+    ImVec2                      HostBackupCurrLineSize;     // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable()
+    ImVec2                      HostBackupCursorMaxPos;     // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable()
+    ImVec1                      HostBackupColumnsOffset;    // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable()
+    float                       HostBackupItemWidth;        // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable()
+    int                         HostBackupItemWidthStackSize;// Backup of OuterWindow->DC.ItemWidthStack.Size 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
+    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)
+    ImGuiTableColumnSortSpecs   SortSpecsSingle;
+    ImVector<ImGuiTableColumnSortSpecs> SortSpecsMulti;     // FIXME-OPT: Using a small-vector pattern would work be good.
+    ImGuiTableSortSpecs         SortSpecs;                  // Public facing sorts specs, this is what we return in TableGetSortSpecs()
+    ImGuiTableColumnIdx         SortSpecsCount;
+    ImGuiTableColumnIdx         ColumnsEnabledCount;        // Number of enabled columns (<= ColumnsCount)
+    ImGuiTableColumnIdx         ColumnsEnabledFixedCount;   // Number of enabled columns (<= ColumnsCount)
+    ImGuiTableColumnIdx         DeclColumnsCount;           // Count calls to TableSetupColumn()
+    ImGuiTableColumnIdx         HoveredColumnBody;          // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column!
+    ImGuiTableColumnIdx         HoveredColumnBorder;        // Index of column whose right-border is being hovered (for resizing).
+    ImGuiTableColumnIdx         AutoFitSingleStretchColumn; // Index of single stretch column requesting auto-fit.
+    ImGuiTableColumnIdx         ResizedColumn;              // Index of column being resized. Reset when InstanceCurrent==0.
+    ImGuiTableColumnIdx         LastResizedColumn;          // Index of column being resized from previous frame.
+    ImGuiTableColumnIdx         HeldHeaderColumn;           // Index of column header being held.
+    ImGuiTableColumnIdx         ReorderColumn;              // Index of column being reordered. (not cleared)
+    ImGuiTableColumnIdx         ReorderColumnDir;           // -1 or +1
+    ImGuiTableColumnIdx         RightMostEnabledColumn;     // Index of right-most non-hidden column.
+    ImGuiTableColumnIdx         LeftMostStretchedColumnDisplayOrder; // Display order of left-most stretched column.
+    ImGuiTableColumnIdx         ContextPopupColumn;         // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot
+    ImGuiTableColumnIdx         FreezeRowsRequest;          // Requested frozen rows count
+    ImGuiTableColumnIdx         FreezeRowsCount;            // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset)
+    ImGuiTableColumnIdx         FreezeColumnsRequest;       // Requested frozen columns count
+    ImGuiTableColumnIdx         FreezeColumnsCount;         // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset)
+    ImGuiTableColumnIdx         RowCellDataCurrent;         // Index of current RowCellData[] entry in current row
+    ImGuiTableDrawChannelIdx    DummyDrawChannel;           // Redirect non-visible columns here.
+    ImGuiTableDrawChannelIdx    Bg1DrawChannelCurrent;      // For Selectable() and other widgets drawing accross columns after the freezing line. Index within DrawSplitter.Channels[]
+    ImGuiTableDrawChannelIdx    Bg1DrawChannelUnfrozen;
+    bool                        IsLayoutLocked;             // Set by TableUpdateLayout() which is called when beginning the first row.
+    bool                        IsInsideRow;                // Set when inside TableBeginRow()/TableEndRow().
+    bool                        IsInitializing;
+    bool                        IsSortSpecsDirty;
+    bool                        IsUsingHeaders;             // Set when the first row had the ImGuiTableRowFlags_Headers flag.
+    bool                        IsContextPopupOpen;         // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted).
+    bool                        IsSettingsRequestLoad;
+    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                        IsResetAllRequest;
+    bool                        IsResetDisplayOrderRequest;
+    bool                        IsUnfrozen;                 // Set when we got past the frozen row.
+    bool                        MemoryCompacted;
+    bool                        HostSkipItems;              // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis
+
+    IMGUI_API ImGuiTable();
+    IMGUI_API ~ImGuiTable();
+};
+
+// sizeof() ~ 12
+struct ImGuiTableColumnSettings
+{
+    float                   WidthOrWeight;
+    ImGuiID                 UserID;
+    ImGuiTableColumnIdx     Index;
+    ImGuiTableColumnIdx     DisplayOrder;
+    ImGuiTableColumnIdx     SortOrder;
+    ImU8                    SortDirection : 2;
+    ImU8                    IsEnabled : 1; // "Visible" in ini file
+    ImU8                    IsStretch : 1;
+
+    ImGuiTableColumnSettings()
+    {
+        WidthOrWeight = 0.0f;
+        UserID = 0;
+        Index = -1;
+        DisplayOrder = SortOrder = -1;
+        SortDirection = ImGuiSortDirection_None;
+        IsEnabled = 1;
+        IsStretch = 0;
+    }
+};
+
+// 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;              // 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.
+    ImGuiTableColumnIdx         ColumnsCount;
+    ImGuiTableColumnIdx         ColumnsCountMax;        // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher
+    bool                        WantApply;              // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context)
+
+    ImGuiTableSettings()        { memset(this, 0, sizeof(*this)); }
+    ImGuiTableColumnSettings*   GetColumnSettings()     { return (ImGuiTableColumnSettings*)(this + 1); }
+};
+
 #endif // #ifdef IMGUI_HAS_TABLE
 
 //-----------------------------------------------------------------------------
@@ -1977,6 +2283,52 @@ namespace ImGui
     IMGUI_API float         GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm);
     IMGUI_API float         GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset);
 
+    // Tables: Candidates for public API
+    IMGUI_API void          TableOpenContextMenu(int column_n = -1);
+    IMGUI_API void          TableSetColumnWidth(int column_n, float width);
+    IMGUI_API void          TableSetColumnIsEnabled(int column_n, bool enabled);
+    IMGUI_API void          TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs);
+    IMGUI_API int           TableGetHoveredColumn(); // May use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. 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 float         TableGetHeaderRowHeight();
+    IMGUI_API void          TablePushBackgroundChannel();
+    IMGUI_API void          TablePopBackgroundChannel();
+
+    // Tables: Internals
+    IMGUI_API ImGuiTable*   TableFindByID(ImGuiID id);
+    IMGUI_API bool          BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f);
+    IMGUI_API void          TableBeginInitMemory(ImGuiTable* table, int columns_count);
+    IMGUI_API void          TableBeginApplyRequests(ImGuiTable* table);
+    IMGUI_API void          TableSetupDrawChannels(ImGuiTable* table);
+    IMGUI_API void          TableUpdateLayout(ImGuiTable* table);
+    IMGUI_API void          TableUpdateBorders(ImGuiTable* table);
+    IMGUI_API void          TableDrawBorders(ImGuiTable* table);
+    IMGUI_API void          TableDrawContextMenu(ImGuiTable* table);
+    IMGUI_API void          TableMergeDrawChannels(ImGuiTable* table);
+    IMGUI_API void          TableSortSpecsSanitize(ImGuiTable* table);
+    IMGUI_API void          TableSortSpecsBuild(ImGuiTable* table);
+    IMGUI_API void          TableFixColumnSortDirection(ImGuiTableColumn* column);
+    IMGUI_API void          TableBeginRow(ImGuiTable* table);
+    IMGUI_API void          TableEndRow(ImGuiTable* table);
+    IMGUI_API void          TableBeginCell(ImGuiTable* table, int column_n);
+    IMGUI_API void          TableEndCell(ImGuiTable* table);
+    IMGUI_API ImRect        TableGetCellBgRect(const ImGuiTable* table, int column_n);
+    IMGUI_API const char*   TableGetColumnName(const ImGuiTable* table, int column_n);
+    IMGUI_API ImGuiID       TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no = 0);
+    IMGUI_API void          TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n);
+    IMGUI_API void          TableSetColumnWidthAutoAll(ImGuiTable* table);
+    IMGUI_API void          TableRemove(ImGuiTable* table);
+    IMGUI_API void          TableGcCompactTransientBuffers(ImGuiTable* table);
+    IMGUI_API void          TableGcCompactSettings();
+
+    // Tables: Settings
+    IMGUI_API void                  TableLoadSettings(ImGuiTable* table);
+    IMGUI_API void                  TableSaveSettings(ImGuiTable* table);
+    IMGUI_API void                  TableResetSettings(ImGuiTable* table);
+    IMGUI_API ImGuiTableSettings*   TableGetBoundSettings(ImGuiTable* table);
+    IMGUI_API void                  TableSettingsInstallHandler(ImGuiContext* context);
+    IMGUI_API ImGuiTableSettings*   TableSettingsCreate(ImGuiID id, int columns_count);
+    IMGUI_API ImGuiTableSettings*   TableSettingsFindByID(ImGuiID id);
+
     // 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 +2446,8 @@ 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          DebugNodeTableSettings(ImGuiTableSettings* settings);
     IMGUI_API void          DebugNodeWindow(ImGuiWindow* window, const char* label);
     IMGUI_API void          DebugNodeWindowSettings(ImGuiWindowSettings* settings);
     IMGUI_API void          DebugNodeWindowsList(ImVector<ImGuiWindow*>* windows, const char* label);

+ 3709 - 0
imgui_tables.cpp

@@ -0,0 +1,3709 @@
+// dear imgui, v1.80 WIP
+// (tables and columns code)
+
+/*
+
+Index of this file:
+
+// [SECTION] Tables: Main code
+// [SECTION] Tables: Drawing
+// [SECTION] Tables: Sorting
+// [SECTION] Tables: Headers
+// [SECTION] Tables: Context Menu
+// [SECTION] Tables: Settings (.ini data)
+// [SECTION] Tables: Garbage Collection
+// [SECTION] Tables: Debugging
+// [SECTION] Columns, BeginColumns, EndColumns, etc.
+
+*/
+
+//-----------------------------------------------------------------------------
+// Typical tables call flow: (root level is generally public API):
+//-----------------------------------------------------------------------------
+// - BeginTable()                               user begin into a table
+//    | BeginChild()                            - (if ScrollX/ScrollY is set)
+//    | TableBeginApplyRequests()               - apply queued resizing/reordering/hiding requests
+//    | - TableSetColumnWidth()                 - apply resizing width (for mouse resize, often requested by previous frame)
+//    |    - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width
+// - TableSetupColumn()                         user submit columns details (optional)
+// - TableSetupScrollFreeze()                   user submit scroll freeze information (optional)
+// - TableUpdateLayout() [Internal]             automatically called by the FIRST call to TableNextRow() or TableHeadersRow(): lock all widths, columns positions, clipping rectangles
+//    | TableSetupDrawChannels()                - setup ImDrawList channels
+//    | TableUpdateBorders()                    - detect hovering columns for resize, ahead of contents submission
+//    | TableDrawContextMenu()                  - draw right-click context menu
+//-----------------------------------------------------------------------------
+// - 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()                             user begin into a new row (also automatically called by TableHeadersRow())
+//    | TableEndRow()                           - finish existing row
+//    | TableBeginRow()                         - add a new row
+// - TableSetColumnIndex() / TableNextColumn()  user begin into a cell
+//    | TableEndCell()                          - close existing column/cell
+//    | TableBeginCell()                        - enter into current column/cell
+// - [...]                                      user emit contents
+//-----------------------------------------------------------------------------
+// - EndTable()                                 user ends the table
+//    | TableDrawBorders()                      - draw outer borders, inner vertical borders
+//    | TableMergeDrawChannels()                - merge draw channels if clipping isn't required
+//    | EndChild()                              - (if ScrollX/ScrollY is set)
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// TABLE SIZING
+//-----------------------------------------------------------------------------
+// (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(). 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':
+//   With ScrollX:
+//   - inner_width  < 0.0f  ->  *illegal* fit in known width (right align from outer_size.x) <-- weird
+//   - 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          ->  *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.
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// TABLES CULLING
+//-----------------------------------------------------------------------------
+// About clipping/culling of Rows in Tables:
+// - For large numbers of rows, it is recommended you use ImGuiListClipper to only submit visible rows.
+//   ImGuiListClipper is reliant on the fact that rows are of equal height.
+//   See 'Demo->Tables->Vertical Scrolling' or 'Demo->Tables->Advanced' for a demo of using the clipper.
+//-----------------------------------------------------------------------------
+// About clipping/culling of Columns in Tables:
+// - Case A: column is not hidden by user, and at least partially in sight (most common case).
+// - Case B: column is clipped / out of sight (because of scrolling or parent ClipRect): TableNextColumn() return false as a hint but we still allow layout output.
+// - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.).
+//
+//                        [A]         [B]          [C]         
+//  TableNextColumn():    true        false        false       -> [userland] when TableNextColumn() / TableSetColumnIndex() return false, user can skip submitting items but only if the column doesn't contribute to row height.
+//          SkipItems:    false       false        true        -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no layout output.
+//           ClipRect:    normal      zero-width   zero-width  -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out mid-way.
+//  ImDrawList output:    normal      dummy        dummy       -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted (because cliprect is zero-width anyway).
+//
+// - We need distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row.
+//   However, in the majority of cases, the contribution to row height is the same for all columns, or the tallest cells are known by the programmer.
+//-----------------------------------------------------------------------------
+// About clipping/culling of whole Tables:
+// - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false.
+//-----------------------------------------------------------------------------
+
+#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"
+
+// System includes
+#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
+#include <stddef.h>     // intptr_t
+#else
+#include <stdint.h>     // intptr_t
+#endif
+
+//-------------------------------------------------------------------------
+// Warnings
+//-------------------------------------------------------------------------
+
+// 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 "-Wformat-nonliteral"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
+#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 "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
+#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] Tables: Main code
+//-----------------------------------------------------------------------------
+
+// Configuration
+static const int TABLE_DRAW_CHANNEL_BG0 = 0;
+static const int TABLE_DRAW_CHANNEL_BG1_FROZEN = 1;
+static const int TABLE_DRAW_CHANNEL_UNCLIPPED = 2;                  // When using ImGuiTableFlags_NoClip
+static const float TABLE_BORDER_SIZE                     = 1.0f;    // FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering.
+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, ImGuiWindow* outer_window)
+{
+    // Adjust flags: set default sizing policy
+    if ((flags & (ImGuiTableFlags_ColumnsWidthStretch | ImGuiTableFlags_ColumnsWidthFixed)) == 0)
+        flags |= (flags & ImGuiTableFlags_ScrollX) ? ImGuiTableFlags_ColumnsWidthFixed : ImGuiTableFlags_ColumnsWidthStretch;
+
+    // Adjust flags: MultiSortable automatically enable Sortable
+    if (flags & ImGuiTableFlags_MultiSortable)
+        flags |= ImGuiTableFlags_Sortable;
+
+    // Adjust flags: disable Resizable when using SameWidths (done above enforcing BordersInnerV)
+    if (flags & ImGuiTableFlags_SameWidths)
+        flags = (flags & ~ImGuiTableFlags_Resizable) | ImGuiTableFlags_NoKeepColumnsVisible;
+
+    // Adjust flags: enforce borders when resizable
+    if (flags & ImGuiTableFlags_Resizable)
+        flags |= ImGuiTableFlags_BordersInnerV;
+
+    // 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: NoBordersInBodyUntilResize takes priority over NoBordersInBody
+    if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize)
+        flags &= ~ImGuiTableFlags_NoBordersInBody;
+
+    // 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;
+
+    // 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;
+
+    return flags;
+}
+
+ImGuiTable::ImGuiTable()
+{
+    memset(this, 0, sizeof(*this));
+    SettingsOffset = -1;
+    InstanceInteracted = -1;
+    LastFrameActive = -1;
+    LastResizedColumn = -1;
+    ContextPopupColumn = -1;
+    ReorderColumn = -1;
+    ResizedColumn = -1;
+    AutoFitSingleStretchColumn = -1;
+    HoveredColumnBody = HoveredColumnBorder = -1;
+}
+
+ImGuiTable::~ImGuiTable()
+{
+    IM_FREE(RawData);
+}
+
+ImGuiTable* ImGui::TableFindByID(ImGuiID id)
+{
+    ImGuiContext& g = *GImGui;
+    return g.Tables.GetByKey(id);
+}
+
+// Read about "TABLE SIZING" at the top of this file.
+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();
+    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 1..64 columns allowed!");
+    if (flags & ImGuiTableFlags_ScrollX)
+        IM_ASSERT(inner_width >= 0.0f);
+
+    // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve.
+    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 (use_child_window && IsClippedEx(outer_rect, 0, false))
+    {
+        ItemSize(outer_rect);
+        return false;
+    }
+
+    // Acquire storage for the table
+    ImGuiTable* table = g.Tables.GetOrAddByKey(id);
+    const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1;
+    const ImGuiID instance_id = id + instance_no;
+    const ImGuiTableFlags table_last_flags = table->Flags;
+    if (instance_no > 0)
+        IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID");
+
+    // Fix flags
+    flags = TableFixFlags(flags, outer_window);
+
+    // Initialize
+    table->ID = id;
+    table->Flags = flags;
+    table->InstanceCurrent = (ImS16)instance_no;
+    table->LastFrameActive = g.FrameCount;
+    table->OuterWindow = table->InnerWindow = outer_window;
+    table->ColumnsCount = columns_count;
+    table->IsLayoutLocked = false;
+    table->InnerWidth = inner_width;
+    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
+        // (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 ((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)
+            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 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-using and not-child-using tables
+    PushOverrideID(instance_id);
+
+    // 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->HostBackupWorkRect = inner_window->WorkRect;
+    table->HostBackupParentWorkRect = inner_window->ParentWorkRect;
+    table->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset;
+    table->HostBackupPrevLineSize = inner_window->DC.PrevLineSize;
+    table->HostBackupCurrLineSize = inner_window->DC.CurrLineSize;
+    table->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos;
+    table->HostBackupItemWidth = outer_window->DC.ItemWidth;
+    table->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size;
+    inner_window->ParentWorkRect = table->WorkRect;
+    inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
+
+    // Padding and Spacing
+    // - None               ........Content..... Pad .....Content........
+    // - PadOuter           | Pad ..Content..... Pad .....Content.. Pad |
+    // - PadInner           ........Content.. Pad | Pad ..Content........
+    // - PadOuter+PadInner  | Pad ..Content.. Pad | Pad ..Content.. Pad |
+    const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0;
+    const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true;
+    const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f;
+    const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f;
+    const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f;
+    const float inner_spacing = inner_spacing_for_border + inner_spacing_explicit;
+    table->CellSpacingX1 = ImCeil(inner_spacing * 0.5f);
+    table->CellSpacingX2 = inner_spacing - table->CellSpacingX1;
+    table->CellPaddingX = inner_padding_explicit;
+    table->CellPaddingY = g.Style.CellPadding.y;
+
+    const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
+    const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f;
+    table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX;
+
+    table->CurrentColumn = -1;
+    table->CurrentRow = -1;
+    table->RowBgColorCounter = 0;
+    table->LastRowFlags = ImGuiTableRowFlags_None;
+    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.ClipWithFull(table->HostClipRect);
+    table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y;
+
+    // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default.
+    // This is because all our cell highlight are manually clipped with BgClipRect
+    // Larger at first, if/after unfreezing will become same as tight
+    table->BgClipRect = table->InnerClipRect;
+    table->BgClipRectForDrawCmd = table->HostClipRect;
+    IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y);
+
+    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 = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any
+    table->FreezeColumnsRequest = table->FreezeColumnsCount = 0;
+    table->IsUnfrozen = true;
+    table->DeclColumnsCount = 0;
+    table->RightMostEnabledColumn = -1;
+
+    // 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);
+
+    // Make table current
+    const int table_idx = g.Tables.GetIndex(table);
+    g.CurrentTableStack.push_back(ImGuiPtrOrIndex(table_idx));
+    g.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.CurrentTableIdx = table_idx;
+
+    if ((table_last_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0)
+        table->IsResetDisplayOrderRequest = true;
+
+    // Mark as used
+    if (table_idx >= g.TablesLastTimeActive.Size)
+        g.TablesLastTimeActive.resize(table_idx + 1, -1.0f);
+    g.TablesLastTimeActive[table_idx] = (float)g.Time;
+    table->MemoryCompacted = false;
+
+    // Setup memory buffer (clear data if columns count changed)
+    const int stored_size = table->Columns.size();
+    if (stored_size != 0 && stored_size != columns_count)
+    {
+        IM_FREE(table->RawData);
+        table->RawData = NULL;
+    }
+    if (table->RawData == NULL)
+    {
+        TableBeginInitMemory(table, columns_count);
+        table->IsInitializing = table->IsSettingsRequestLoad = true;
+    }
+    if (table->IsResetAllRequest)
+        TableResetSettings(table);
+    if (table->IsInitializing)
+    {
+        // Initialize for new settings
+        table->SettingsOffset = -1;
+        table->IsSortSpecsDirty = true;
+        for (int n = 0; n < columns_count; n++)
+        {
+            ImGuiTableColumn* column = &table->Columns[n];
+            float width_auto = column->WidthAuto;
+            *column = ImGuiTableColumn();
+            column->WidthAuto = width_auto;
+            column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker
+            column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n;
+            column->IsEnabled = column->IsEnabledNextFrame = true;
+        }
+    }
+
+    // Load settings
+    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 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;
+
+    // Clear names
+    // FIXME-TABLES: probably could be done differently...
+    if (table->ColumnsNames.Buf.Size > 0)
+    {
+        table->ColumnsNames.Buf.resize(0);
+        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+        {
+            ImGuiTableColumn* column = &table->Columns[column_n];
+            column->NameOffset = -1;
+        }
+    }
+
+    // Apply queued resizing/reordering/hiding requests
+    TableBeginApplyRequests(table);
+
+    return true;
+}
+
+// For reference, the average total _allocation count_ for a table is:
+// + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables)
+// + 1 (for table->RawData allocated below)
+// + 1 (for table->ColumnsNames, if names are used)
+// + 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 TableSetupDrawChannels() for details.
+// Unused channels don't perform their +2 allocations.
+void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count)
+{
+    // Allocate single buffer for our arrays
+    ImSpanAllocator<3> span_allocator;
+    span_allocator.ReserveBytes(0, columns_count * sizeof(ImGuiTableColumn));
+    span_allocator.ReserveBytes(1, columns_count * sizeof(ImGuiTableColumnIdx));
+    span_allocator.ReserveBytes(2, columns_count * sizeof(ImGuiTableCellData));
+    table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes());
+    memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes());
+    span_allocator.SetArenaBasePtr(table->RawData);
+    span_allocator.GetSpan(0, &table->Columns);
+    span_allocator.GetSpan(1, &table->DisplayOrderToIndex);
+    span_allocator.GetSpan(2, &table->RowCellData);
+}
+
+// Apply queued resizing/reordering/hiding requests
+void ImGui::TableBeginApplyRequests(ImGuiTable* table)
+{
+    // Handle resizing request
+    // (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->InstanceCurrent == 0)
+    {
+        if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX)
+            TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth);
+        table->LastResizedColumn = table->ResizedColumn;
+        table->ResizedColumnNextWidth = FLT_MAX;
+        table->ResizedColumn = -1;
+
+        // Process auto-fit for single stretch column, which is a special case
+        // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings.
+        if (table->AutoFitSingleStretchColumn != -1)
+        {
+            TableSetColumnWidth(table->AutoFitSingleStretchColumn, table->Columns[table->AutoFitSingleStretchColumn].WidthAuto);
+            table->AutoFitSingleStretchColumn = -1;
+        }
+    }
+
+    // Handle reordering request
+    // Note: we don't clear ReorderColumn after handling the request.
+    if (table->InstanceCurrent == 0)
+    {
+        if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1)
+            table->ReorderColumn = -1;
+        table->HeldHeaderColumn = -1;
+        if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0)
+        {
+            // 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* src_column = &table->Columns[table->ReorderColumn];
+            ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn];
+            IM_UNUSED(dst_column);
+            const int src_order = src_column->DisplayOrder;
+            const int dst_order = dst_column->DisplayOrder;
+            src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order;
+            for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir)
+                table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)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->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
+            table->ReorderColumnDir = 0;
+            table->IsSettingsDirty = true;
+        }
+    }
+
+    // Handle display order reset request
+    if (table->IsResetDisplayOrderRequest)
+    {
+        for (int n = 0; n < table->ColumnsCount; n++)
+            table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n;
+        table->IsResetDisplayOrderRequest = false;
+        table->IsSettingsDirty = true;
+    }
+}
+
+// Adjust flags: default width mode + stretch columns are not allowed when auto extending
+static ImGuiTableColumnFlags TableFixColumnFlags(ImGuiTable* table, ImGuiTableColumnFlags flags)
+{
+    // Sizing Policy
+    if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0)
+    {
+        // FIXME-TABLE: Inconsistent to promote columns to WidthAutoResize
+        if (table->Flags & ImGuiTableFlags_ColumnsWidthFixed)
+            flags |= ((table->Flags & ImGuiTableFlags_Resizable) && !(flags & ImGuiTableColumnFlags_NoResize)) ? ImGuiTableColumnFlags_WidthFixed : ImGuiTableColumnFlags_WidthAutoResize;
+        else
+            flags |= ImGuiTableColumnFlags_WidthStretch;
+    }
+    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used.
+    if (flags & ImGuiTableColumnFlags_WidthAutoResize)
+        flags |= ImGuiTableColumnFlags_NoResize;
+
+    // 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;
+}
+
+// Minimum column content width (without padding)
+static float TableGetMinColumnWidth()
+{
+    ImGuiContext& g = *GImGui;
+    //return g.Style.ColumnsMinSpacing; // FIXME-TABLE
+    return g.Style.FramePadding.x * 1.0f;
+}
+
+// Layout columns for the frame. This is in essence the followup to BeginTable().
+// 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 WidthAutoResize
+// columns, increase feedback side-effect with widgets relying on WorkRect.Max.x. Maybe provide a default distribution
+// for WidthAutoResize columns?
+void ImGui::TableUpdateLayout(ImGuiTable* table)
+{
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(table->IsLayoutLocked == false);
+
+    // [Part 1] Apply/lock Enabled and Order states.
+    // Process columns in their visible orders as we are building the Prev/Next indices.
+    int last_visible_column_idx = -1;
+    bool want_column_auto_fit = false;
+    table->IsDefaultDisplayOrder = true;
+    table->ColumnsEnabledCount = 0;
+    table->EnabledMaskByIndex = 0x00;
+    table->EnabledMaskByDisplayOrder = 0x00;
+    for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
+    {
+        const int column_n = table->DisplayOrderToIndex[order_n];
+        if (column_n != order_n)
+            table->IsDefaultDisplayOrder = false;
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide))
+            column->IsEnabledNextFrame = true;
+        if (column->IsEnabled != column->IsEnabledNextFrame)
+        {
+            column->IsEnabled = column->IsEnabledNextFrame;
+            table->IsSettingsDirty = true;
+            if (!column->IsEnabled && column->SortOrder != -1)
+                table->IsSortSpecsDirty = true;
+        }
+        if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_MultiSortable))
+            table->IsSortSpecsDirty = true;
+
+        bool start_auto_fit = false;
+        if (column->Flags & (ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAutoResize))
+            start_auto_fit = column->WidthRequest < 0.0f;
+        else
+            start_auto_fit = column->StretchWeight < 0.0f;
+        if (start_auto_fit)
+            column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames
+
+        if (column->AutoFitQueue != 0x00)
+            want_column_auto_fit = true;
+
+        ImU64 index_mask = (ImU64)1 << column_n;
+        ImU64 display_order_mask = (ImU64)1 << column->DisplayOrder;
+        if (column->IsEnabled)
+        {
+            column->PrevEnabledColumn = (ImGuiTableColumnIdx)last_visible_column_idx;
+            column->NextEnabledColumn = -1;
+            if (last_visible_column_idx != -1)
+                table->Columns[last_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n;
+            column->IndexWithinEnabledSet = table->ColumnsEnabledCount;
+            table->ColumnsEnabledCount++;
+            table->EnabledMaskByIndex |= index_mask;
+            table->EnabledMaskByDisplayOrder |= display_order_mask;
+            last_visible_column_idx = column_n;
+        }
+        else
+        {
+            column->IndexWithinEnabledSet = -1;
+        }
+        IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder);
+    }
+    table->RightMostEnabledColumn = (ImGuiTableColumnIdx)last_visible_column_idx;
+
+    // [Part 2] 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).
+    // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time.
+    if (want_column_auto_fit && table->OuterWindow != table->InnerWindow)
+        table->InnerWindow->SkipItems = false;
+    if (want_column_auto_fit)
+        table->IsSettingsDirty = true;
+
+    // [Part 3] Fix column flags. Calculate ideal width for columns. Count how many fixed/stretch columns we have and sum of weights.
+    const float min_column_width = TableGetMinColumnWidth();
+    const float min_column_distance = min_column_width + table->CellPaddingX * 2.0f + table->CellSpacingX1 + table->CellSpacingX2;
+    int count_fixed = 0;                    // Number of columns that have fixed sizing policy (not stretched sizing policy) (this is NOT the opposite of count_resizable!)
+    int count_resizable = 0;                // Number of columns the user can resize (this is NOT the opposite of count_fixed!)
+    float sum_weights_stretched = 0.0f;     // Sum of all weights for weighted columns.
+    float sum_width_fixed_requests = 0.0f;  // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns.
+    float max_width_auto = 0.0f;            // Largest auto-width (used for SameWidths feature)
+    table->LeftMostStretchedColumnDisplayOrder = -1;
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
+            continue;
+        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) | (column->Flags & ImGuiTableColumnFlags_StatusMask_);
+        if ((column->Flags & ImGuiTableColumnFlags_IndentMask_) == 0)
+            column->Flags |= (column_n == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable;
+        if ((column->Flags & ImGuiTableColumnFlags_NoResize) == 0)
+            count_resizable++;
+
+        // 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);
+
+        // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping)
+        // Combine width from regular rows + width from headers unless requested not to.
+        if (!column->IsPreserveWidthAuto)
+        {
+            const float content_width_body = (float)ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX;
+            const float content_width_headers = (float)column->ContentMaxXHeadersIdeal - column->WorkMinX;
+            float width_auto = content_width_body;
+            if (!(table->Flags & ImGuiTableFlags_NoHeadersWidth) && !(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth))
+                width_auto = ImMax(width_auto, content_width_headers);
+            width_auto = ImMax(width_auto, min_column_width);
+
+            // Non-resizable columns also submit their requested width
+            if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f)
+                if (!(table->Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize))
+                    width_auto = ImMax(width_auto, column->InitStretchWeightOrWidth);
+
+            column->WidthAuto = width_auto;
+        }
+        column->IsPreserveWidthAuto = false;
+
+        if (column->Flags & (ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAutoResize))
+        {
+            // Process auto-fit for non-stretched columns
+            // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!)
+            if ((column->AutoFitQueue != 0x00) || ((column->Flags & ImGuiTableColumnFlags_WidthAutoResize) && column->IsVisibleX))
+                column->WidthRequest = column->WidthAuto;
+
+            // 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: Another constant/scale?
+            count_fixed += 1;
+            sum_width_fixed_requests += column->WidthRequest;
+        }
+        else
+        {
+            IM_ASSERT(column->Flags & ImGuiTableColumnFlags_WidthStretch);
+            if (column->StretchWeight < 0.0f)
+                column->StretchWeight = 1.0f;
+            sum_weights_stretched += column->StretchWeight;
+            if (table->LeftMostStretchedColumnDisplayOrder == -1 || table->LeftMostStretchedColumnDisplayOrder > column->DisplayOrder)
+                table->LeftMostStretchedColumnDisplayOrder = column->DisplayOrder;
+        }
+        max_width_auto = ImMax(max_width_auto, column->WidthAuto);
+        sum_width_fixed_requests += table->CellPaddingX * 2.0f;
+    }
+    table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed;
+
+    // [Part 4] Apply "same widths" feature.
+    // - When all columns are fixed or columns are of mixed type: use the maximum auto width.
+    // - When all columns are stretch: use same weight.
+    const bool mixed_same_widths = (table->Flags & ImGuiTableFlags_SameWidths) && count_fixed > 0;
+    if (table->Flags & ImGuiTableFlags_SameWidths)
+    {
+        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+        {
+            if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
+                continue;
+            ImGuiTableColumn* column = &table->Columns[column_n];
+            if (column->Flags & (ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_WidthAutoResize))
+            {
+                sum_width_fixed_requests += max_width_auto - column->WidthRequest; // Update old sum
+                column->WidthRequest = max_width_auto;
+            }
+            else
+            {
+                sum_weights_stretched += 1.0f - column->StretchWeight; // Update old sum
+                column->StretchWeight = 1.0f;
+                if (count_fixed > 0)
+                    column->WidthRequest = max_width_auto;
+            }
+        }
+    }
+
+    // [Part 5] Apply final widths based on requested widths
+    const ImRect work_rect = table->WorkRect;
+    const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
+    const float width_avail = ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth();
+    const float width_avail_for_stretched_columns = mixed_same_widths ? 0.0f : width_avail - width_spacings - sum_width_fixed_requests;
+    float width_remaining_for_stretched_columns = width_avail_for_stretched_columns;
+    table->ColumnsTotalWidth = width_spacings;
+    table->ColumnsAutoFitWidth = width_spacings;
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
+            continue;
+        ImGuiTableColumn* column = &table->Columns[column_n];
+
+        // Allocate width for stretched/weighted columns
+        if (column->Flags & ImGuiTableColumnFlags_WidthStretch)
+        {
+            // StretchWeight gets converted into WidthRequest
+            if (!mixed_same_widths) 
+            {
+                float weight_ratio = column->StretchWeight / 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 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->NextEnabledColumn != -1)
+                if (ImGuiTableColumn* next_column = &table->Columns[column->NextEnabledColumn])
+                    if (next_column->Flags & ImGuiTableColumnFlags_WidthFixed)
+                        column->Flags |= (next_column->Flags & ImGuiTableColumnFlags_NoDirectResize_);
+        }
+
+        // [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->NextEnabledColumn == -1 && table->LeftMostStretchedColumnDisplayOrder != -1)
+            column->Flags |= ImGuiTableColumnFlags_NoDirectResize_;
+
+        // Assign final width, record width in case we will need to shrink
+        column->WidthGiven = ImFloor(ImMax(column->WidthRequest, min_column_width));
+        table->ColumnsTotalWidth += column->WidthGiven + table->CellPaddingX * 2.0f;
+        table->ColumnsAutoFitWidth += column->WidthAuto + table->CellPaddingX * 2.0f;
+    }
+
+    // [Part 6] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column).
+    // Using right-to-left distribution (more likely to match resizing cursor).
+    if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths))
+        for (int order_n = table->ColumnsCount - 1; sum_weights_stretched > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--)
+        {
+            if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
+                continue;
+            ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]];
+            if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch))
+                continue;
+            column->WidthRequest += 1.0f;
+            column->WidthGiven += 1.0f;
+            width_remaining_for_stretched_columns -= 1.0f;
+        }
+
+    table->HoveredColumnBody = -1;
+    table->HoveredColumnBorder = -1;
+    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);
+
+    // [Part 7] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column
+    // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping.
+    int visible_n = 0;
+    float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1;
+    ImRect host_clip_rect = table->InnerClipRect;
+    //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2;
+    table->VisibleMaskByIndex = 0x00;
+    table->RequestOutputMaskByIndex = 0x00;
+    for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
+    {
+        const int column_n = table->DisplayOrderToIndex[order_n];
+        ImGuiTableColumn* column = &table->Columns[column_n];
+
+        column->NavLayerCurrent = (ImS8)((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;
+
+        // Clear status flags
+        column->Flags &= ~ImGuiTableColumnFlags_StatusMask_;
+
+        if ((table->EnabledMaskByDisplayOrder & ((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.
+            column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x;
+            column->WidthGiven = 0.0f;
+            column->ClipRect.Min.y = work_rect.Min.y;
+            column->ClipRect.Max.y = FLT_MAX;
+            column->ClipRect.ClipWithFull(host_clip_rect);
+            column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false;
+            column->IsSkipItems = true;
+            column->ItemWidth = 1.0f;
+            continue;
+        }
+
+        // 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 = (ImGuiTableColumnIdx)column_n;
+
+        // Maximum width
+        float max_width = FLT_MAX;
+        if (table->Flags & ImGuiTableFlags_ScrollX)
+        {
+            // Frozen columns can't reach beyond visible width else scrolling will naturally break.
+            if (order_n < table->FreezeColumnsRequest)
+            {
+                max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - order_n) * min_column_distance) - offset_x;
+                max_width = max_width - table->OuterPaddingX - table->CellPaddingX - table->CellSpacingX2;
+            }
+        }
+        else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0)
+        {
+            // 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)
+            // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match.
+            // See "table_width_distrib" and "table_width_keep_visible" tests
+            max_width = table->WorkRect.Max.x - (table->ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - offset_x;
+            //max_width -= table->CellSpacingX1;
+            max_width -= table->CellSpacingX2;
+            max_width -= table->CellPaddingX * 2.0f;
+            max_width -= table->OuterPaddingX;
+        }
+        column->WidthGiven = ImMin(column->WidthGiven, max_width);
+
+        // Minimum width
+        column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, min_column_width));
+
+        // Lock all our positions
+        // - ClipRect.Min.x: Because merging draw commands doesn't compare min boundaries, we make ClipRect.Min.x match left bounds to be consistent regardless of merging.
+        // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) is detrimental to visibility in very-small column.
+        // - FIXME-TABLE: We want equal width columns to have equal (ClipRect.Max.x - WorkMinX) width, which means ClipRect.max.x cannot stray off host_clip_rect.Max.x else right-most column may appear shorter.
+        column->MinX = offset_x;
+        column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f;
+        column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1;
+        column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max
+        column->ItemWidth = ImFloor(column->WidthGiven * 0.65f);
+        column->ClipRect.Min.x = column->MinX;
+        column->ClipRect.Min.y = work_rect.Min.y;
+        column->ClipRect.Max.x = column->MaxX; // column->WorkMaxX;
+        column->ClipRect.Max.y = FLT_MAX;
+        column->ClipRect.ClipWithFull(host_clip_rect);
+
+        // Mark column as Clipped (not in sight)
+        // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables.
+        // FIXME-TABLE: Because InnerClipRect.Max.y is conservatively ==outer_window->ClipRect.Max.y, we never can mark columns _Above_ the scroll line as not IsVisibleY.
+        // Taking advantage of LastOuterHeight would yield good results there...
+        // FIXME-TABLE: IsVisible == false is disabled because it effectively means not submitting will reduces contents width which is fed to outer_window->DC.CursorMaxPos.x,
+        // and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, but could be something else).
+        // Possible solution to preserve last known content width for clipped column. Test 'table_reported_size' fails when enabling Y clipping and window is resized small.
+        column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x);
+        column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y);
+        const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY;
+        if (is_visible)
+            table->VisibleMaskByIndex |= ((ImU64)1 << column_n);
+
+        // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output.
+        column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0;
+        if (column->IsRequestOutput)
+            table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n);
+
+        // Mark column as SkipItems (ignoring all items/layout)
+        column->IsSkipItems = !column->IsEnabled || table->HostSkipItems;
+        //if (!is_visible && (column->Flags & ImGuiTableColumnFlags_AutoCull))
+        //    if ((column->AutoFitQueue & 1) == 0 && (column->CannotSkipItemsQueue & 1) == 0)
+        //        column->IsSkipItems = true;
+        if (column->IsSkipItems)
+            IM_ASSERT(!is_visible);
+
+        // Update status flags
+        column->Flags |= ImGuiTableColumnFlags_IsEnabled;
+        if (is_visible)
+            column->Flags |= ImGuiTableColumnFlags_IsVisible;
+        if (column->SortOrder != -1)
+            column->Flags |= ImGuiTableColumnFlags_IsSorted;
+        if (table->HoveredColumnBody == column_n)
+            column->Flags |= ImGuiTableColumnFlags_IsHovered;
+
+        // 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->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen);
+        //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter)
+        //    column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f);
+
+        // Reset content width variables
+        column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX;
+        column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX;
+
+        // Don't decrement auto-fit counters until container window got a chance to submit its items
+        if (table->HostSkipItems == false)
+        {
+            column->AutoFitQueue >>= 1;
+            column->CannotSkipItemsQueue >>= 1;
+        }
+
+        if (visible_n < table->FreezeColumnsCount)
+            host_clip_rect.Min.x = ImMax(host_clip_rect.Min.x, column->MaxX + TABLE_BORDER_SIZE);
+
+        offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f;
+        visible_n++;
+    }
+
+    // [Part 8] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it)
+    // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either
+    // because of using _WidthAutoResize/_WidthStretch). This will hide the resizing option from the context menu.
+    if (is_hovering_table && table->HoveredColumnBody == -1)
+    {
+        float unused_x1 = table->WorkRect.Min.x;
+        if (table->RightMostEnabledColumn != -1)
+            unused_x1 = ImMax(unused_x1, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x);
+        if (g.IO.MousePos.x >= unused_x1)
+            table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount;
+    }
+    if (count_resizable == 0 && (table->Flags & ImGuiTableFlags_Resizable))
+        table->Flags &= ~ImGuiTableFlags_Resizable;
+
+    // [Part 9] Allocate draw channels
+    TableSetupDrawChannels(table);
+
+    // [Part 10] Hit testing on borders
+    if (table->Flags & ImGuiTableFlags_Resizable)
+        TableUpdateBorders(table);
+    table->LastFirstRowHeight = 0.0f;
+    table->IsLayoutLocked = true;
+    table->IsUsingHeaders = false;
+
+    // [Part 11] Context menu
+    if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted)
+    {
+        const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID);
+        if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings))
+        {
+            TableDrawContextMenu(table);
+            EndPopup();
+        }
+        else
+        {
+            table->IsContextPopupOpen = false;
+        }
+    }
+
+    // [Part 13] 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);
+
+    // Initial state
+    ImGuiWindow* inner_window = table->InnerWindow;
+    if (table->Flags & ImGuiTableFlags_NoClip)
+        table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_UNCLIPPED);
+    else
+        inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false);
+}
+
+// Process hit-testing 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 float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS;
+    const float hit_y1 = table->OuterRect.Min.y;
+    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++)
+    {
+        if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
+            continue;
+
+        const int column_n = table->DisplayOrderToIndex[order_n];
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        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, border_y2_hit);
+        //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100));
+        KeepAliveID(column_id);
+
+        bool hovered = false, held = false;
+        bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);
+        if (pressed && IsMouseDoubleClicked(0))
+        {
+            TableSetColumnWidthAutoSingle(table, column_n);
+            ClearActiveID();
+            held = hovered = false;
+        }
+        if (held)
+        {
+            table->ResizedColumn = (ImGuiTableColumnIdx)column_n;
+            table->InstanceInteracted = table->InstanceCurrent;
+        }
+        if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held)
+        {
+            table->HoveredColumnBorder = (ImGuiTableColumnIdx)column_n;
+            SetMouseCursor(ImGuiMouseCursor_ResizeEW);
+        }
+    }
+}
+
+void    ImGui::EndTable()
+{
+    ImGuiContext& g = *GImGui;
+    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)
+    //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?");
+
+    // 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);
+
+    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);
+
+    // 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->DC.PrevLineSize = table->HostBackupPrevLineSize;
+    inner_window->DC.CurrLineSize = table->HostBackupCurrLineSize;
+    inner_window->DC.CursorMaxPos = table->HostBackupCursorMaxPos;
+    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();
+
+    if (!(flags & ImGuiTableFlags_NoClip))
+        inner_window->DrawList->PopClipRect();
+    inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back();
+
+    // Draw borders
+    if ((flags & ImGuiTableFlags_Borders) != 0)
+        TableDrawBorders(table);
+    table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0);
+
+    // 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;
+    if (table->RightMostEnabledColumn != -1)
+        max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].MaxX);
+
+#if 0
+    // Strip out dummy channel draw calls
+    // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out)
+    // (The problem with this approach is we are effectively making it harder for users watching metrics to spot wasted vertices)
+    if (table->DummyDrawChannel != (ImGuiTableColumnIdx)-1)
+    {
+        ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel];
+        dummy_channel->_CmdBuffer.resize(0);
+        dummy_channel->_IdxBuffer.resize(0);
+    }
+#endif
+
+    // Flatten channels and merge draw calls
+    if ((table->Flags & ImGuiTableFlags_NoClip) == 0)
+        TableMergeDrawChannels(table);
+    table->DrawSplitter.Merge(inner_window->DrawList);
+
+    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 && table->InstanceInteracted == table->InstanceCurrent)
+    {
+        // When releasing a column being resized, scroll to keep the resulting column in sight
+        const float neighbor_width_to_keep_visible = TableGetMinColumnWidth() + table->CellPaddingX * 2.0f;
+        ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn];
+        if (column->MaxX < table->InnerClipRect.Min.x)
+            SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f);
+        else if (column->MaxX > table->InnerClipRect.Max.x)
+            SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f);
+    }
+
+    // Apply resizing/dragging at the end of the frame
+    if (table->ResizedColumn != -1 && table->InstanceCurrent == table->InstanceInteracted)
+    {
+        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 - table->CellSpacingX1 - table->CellPaddingX * 2.0f);
+        table->ResizedColumnNextWidth = new_width;
+    }
+
+    // Layout in outer window
+    IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!");
+    IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= table->HostBackupItemWidthStackSize, "Too many PopItemWidth!");
+    PopID();
+    inner_window->WorkRect = table->HostBackupWorkRect;
+    inner_window->ParentWorkRect = table->HostBackupParentWorkRect;
+    inner_window->SkipItems = table->HostSkipItems;
+    outer_window->DC.CursorPos = table->OuterRect.Min;
+    outer_window->DC.ItemWidth = table->HostBackupItemWidth;
+    outer_window->DC.ItemWidthStack.Size = table->HostBackupItemWidthStackSize;
+    outer_window->DC.ColumnsOffset = table->HostBackupColumnsOffset;
+    if (inner_window != outer_window)
+    {
+        EndChild();
+    }
+    else
+    {
+        ImVec2 item_size = table->OuterRect.GetSize();
+        item_size.x = table->ColumnsTotalWidth;
+        ItemSize(item_size);
+    }
+
+    // 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 + 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);
+    table->IsInitializing = false;
+
+    // Clear or restore current table, if any
+    IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table);
+    g.CurrentTableStack.pop_back();
+    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;
+}
+
+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->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))
+            continue;
+        IM_ASSERT(column->StretchWeight > 0.0f);
+        visible_weight += column->StretchWeight;
+        visible_width += column->WidthRequest;
+    }
+    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->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))
+            continue;
+        column->StretchWeight = ((column->WidthRequest + 0.0f) / visible_width) * visible_weight;
+    }
+}
+
+// 'width' = inner column width, without padding
+void ImGui::TableSetColumnWidth(int column_n, float width)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    IM_ASSERT(table != NULL && table->IsLayoutLocked == false);
+    IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
+    ImGuiTableColumn* column_0 = &table->Columns[column_n];
+    float column_0_width = width;
+
+    // Constraints
+    const 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->ColumnsEnabledCount - (column_0->IndexWithinEnabledSet + 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->WidthRequest == column_0_width)
+        return;
+
+    ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : 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 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)
+    // - 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 TableUpdateLayout().
+    // - [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
+        // 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)
+            {
+                // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)
+                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->WidthRequest = column_0_width;
+    }
+    else if (column_0->Flags & ImGuiTableColumnFlags_WidthStretch)
+    {
+        // We can also use previous column if there's no next one
+        if (column_1 == NULL)
+            column_1 = (column_0->PrevEnabledColumn != -1) ? &table->Columns[column_0->PrevEnabledColumn] : NULL;
+        if (column_1 == NULL)
+            return;
+
+        if (column_1->Flags & ImGuiTableColumnFlags_WidthFixed)
+        {
+            // [Resize Rule 2]
+            float off = (column_0->WidthGiven - column_0_width);
+            float column_1_width = column_1->WidthGiven + off;
+            column_1->WidthRequest = ImMax(min_width, column_1_width);
+        }
+        else
+        {
+            // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)
+            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);
+        }
+    }
+}
+
+// We use a default parameter of 'init_width_or_weight == -1',
+// - with ImGuiTableColumnFlags_WidthFixed,    width  <= 0 --> init width == auto
+// - with ImGuiTableColumnFlags_WidthFixed,    width  >  0 --> init width == manual
+// - with ImGuiTableColumnFlags_WidthStretch,  weight <  0 --> init weight == 1.0f
+// - with ImGuiTableColumnFlags_WidthStretch,  weight >= 0 --> init weight == custom
+// Widths are specified _without_ CellPadding. So if you specify a width of 100.0f the column will be 100.0f+Padding*2.0f and you can fit a 100.0-wide item in it.
+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 && "Need to call TableSetupColumn() after BeginTable()!");
+    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!");
+    IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()");
+
+    ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount];
+    table->DeclColumnsCount++;
+
+    // When passing a width automatically enforce WidthFixed policy
+    // (vs TableFixColumnFlags would default to WidthAutoResize)
+    // (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_ColumnsWidthFixed) && (init_width_or_weight > 0.0f))
+            flags |= ImGuiTableColumnFlags_WidthFixed;
+
+    column->UserID = user_id;
+    column->FlagsIn = flags;
+    column->Flags = TableFixColumnFlags(table, column->FlagsIn) | (column->Flags & ImGuiTableColumnFlags_StatusMask_);
+    flags = column->Flags;
+
+    // Initialize defaults
+    if (flags & ImGuiTableColumnFlags_WidthStretch)
+        IM_ASSERT(init_width_or_weight != 0.0f && "Need to provide a valid weight!");
+    column->InitStretchWeightOrWidth = init_width_or_weight;
+    if (table->IsInitializing && column->WidthRequest < 0.0f && column->StretchWeight < 0.0f)
+    {
+        // Init width or weight
+        if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f)
+            column->WidthRequest = init_width_or_weight;
+        if (flags & ImGuiTableColumnFlags_WidthStretch)
+            column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : 1.0f;
+
+        // Disable auto-fit if an explicit width/weight has been specified
+        if (init_width_or_weight > 0.0f)
+            column->AutoFitQueue = 0x00;
+    }
+    if (table->IsInitializing)
+    {
+        // Init default visibility/sort state
+        if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0)
+            column->IsEnabled = column->IsEnabledNextFrame = 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.
+            column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending);
+        }
+    }
+
+    // Store name (append with zero-terminator in contiguous buffer)
+    IM_ASSERT(column->NameOffset == -1);
+    if (label != NULL && label[0] != 0)
+    {
+        column->NameOffset = (ImS16)table->ColumnsNames.size();
+        table->ColumnsNames.append(label, label + strlen(label) + 1);
+    }
+}
+
+// [Public]
+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 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) ? (ImGuiTableColumnIdx)columns : 0;
+    table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0;
+    table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0;
+    table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0;
+    table->IsUnfrozen = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b
+}
+
+// [Public] Starts into the first cell of a new row
+void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+
+    if (!table->IsLayoutLocked)
+        TableUpdateLayout(table);
+    if (table->IsInsideRow)
+        TableEndRow(table);
+
+    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 + row_min_height);
+
+    // Disable output until user calls TableNextColumn()
+    table->InnerWindow->SkipItems = true;
+}
+
+// [Internal] Called by TableNextRow()!
+void ImGui::TableBeginRow(ImGuiTable* table)
+{
+    ImGuiWindow* window = table->InnerWindow;
+    IM_ASSERT(!table->IsInsideRow);
+
+    // New row
+    table->CurrentRow++;
+    table->CurrentColumn = -1;
+    table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE;
+    table->RowCellDataCurrent = -1;
+    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;
+    table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent
+    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.
+    if (table->RowFlags & ImGuiTableRowFlags_Headers)
+    {
+        TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg));
+        if (table->CurrentRow == 0)
+            table->IsUsingHeaders = true;
+    }
+}
+
+// [Internal] Called by TableNextRow()!
+void ImGui::TableEndRow(ImGuiTable* table)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    IM_ASSERT(window == table->InnerWindow);
+    IM_ASSERT(table->IsInsideRow);
+
+    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.
+    window->DC.CursorPos.y = table->RowPosY2;
+
+    // Row background fill
+    const float bg_y1 = table->RowPosY1;
+    const float bg_y2 = table->RowPosY2;
+
+    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 = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y);
+    if (is_visible)
+    {
+        // Decide of background color for the row
+        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_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;
+        const float border_size = TABLE_BORDER_SIZE;
+        if (table->CurrentRow > 0 || table->InnerWindow == table->OuterWindow)
+            if (table->Flags & ImGuiTableFlags_BordersInnerH)
+                border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight;
+
+        const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0;
+        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 SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is
+            // always followed by a change of clipping rectangle we perform the smallest overwrite possible here.
+            if ((table->Flags & ImGuiTableFlags_NoClip) == 0)
+                window->DrawList->_CmdHeader.ClipRect = table->BgClipRectForDrawCmd.ToVec4();
+            table->DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0);
+        }
+
+        // Draw row background
+        // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle
+        if (bg_col0 || bg_col1)
+        {
+            ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2);
+            row_rect.ClipWith(table->BgClipRect);
+            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->RowCellDataCurrent];
+            for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++)
+            {
+                const ImGuiTableColumn* column = &table->Columns[cell_data->Column];
+                ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column);
+                cell_bg_rect.ClipWith(table->BgClipRect);
+                cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x);     // So that first column after frozen one gets clipped
+                cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX);
+                window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor);
+            }
+        }
+
+        // Draw top border
+        if (border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y)
+            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_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y)
+            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)
+    // 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_request)
+        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+        {
+            ImGuiTableColumn* column = &table->Columns[column_n];
+            column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
+        }
+    if (unfreeze_rows_actual)
+    {
+        IM_ASSERT(table->IsUnfrozen == false);
+        table->IsUnfrozen = true;
+
+        // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect
+        float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y);
+        table->BgClipRect.Min.y = table->BgClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y);
+        table->BgClipRect.Max.y = table->BgClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y;
+        table->Bg1DrawChannelCurrent = table->Bg1DrawChannelUnfrozen;
+        IM_ASSERT(table->BgClipRectForDrawCmd.Min.y <= table->BgClipRectForDrawCmd.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;
+        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->DrawChannelUnfrozen;
+            column->ClipRect.Min.y = table->BgClipRectForDrawCmd.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))
+        table->RowBgColorCounter++;
+    table->IsInsideRow = false;
+}
+
+// [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)
+{
+    ImGuiTableColumn* column = &table->Columns[column_n];
+    ImGuiWindow* window = table->InnerWindow;
+    table->CurrentColumn = column_n;
+
+    // Start position is roughly ~~ CellRect.Min + CellPadding + Indent
+    float start_x = column->WorkMinX;
+    if (column->Flags & ImGuiTableColumnFlags_IndentEnable)
+        start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row.
+
+    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->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent;
+
+    window->WorkRect.Min.y = window->DC.CursorPos.y;
+    window->WorkRect.Min.x = column->WorkMinX;
+    window->WorkRect.Max.x = column->WorkMaxX;
+    window->DC.ItemWidth = column->ItemWidth;
+
+    // To allow ImGuiListClipper to function we propagate our row height
+    if (!column->IsEnabled)
+        window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2);
+
+    window->SkipItems = column->IsSkipItems;
+    if (table->Flags & ImGuiTableFlags_NoClip)
+    {
+        // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.
+        table->DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_UNCLIPPED);
+        //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_UNCLIPPED);
+    }
+    else
+    {
+        // FIXME-TABLE: Could avoid this if draw channel is dummy channel?
+        SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
+        table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
+    }
+}
+
+// [Internal] Called by TableNextRow()/TableNextColumn()!
+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->ContentMaxXHeadersUsed;  // Useful in case user submit contents in header row that is not a TableHeader() call
+    else
+        p_max_pos_x = table->IsUnfrozen ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen;
+    *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x);
+    table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY);
+    column->ItemWidth = window->DC.ItemWidth;
+
+    // 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);
+}
+
+// [Public] Append into the next column/cell
+bool ImGui::TableNextColumn()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    if (!table)
+        return false;
+
+    if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount)
+    {
+        if (table->CurrentColumn != -1)
+            TableEndCell(table);
+        TableBeginCell(table, table->CurrentColumn + 1);
+    }
+    else
+    {
+        TableNextRow();
+        TableBeginCell(table, 0);
+    }
+
+    // Return whether the column is visible. User may choose to skip submitting items based on this return value,
+    // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
+    int column_n = table->CurrentColumn;
+    return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0;
+}
+
+// [Public] Append into a specific column
+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);
+    }
+
+    // Return whether the column is visible. User may choose to skip submitting items based on this return value,
+    // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
+    return (table->RequestOutputMaskByIndex & ((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;
+    ImGuiTable* table = g.CurrentTable;
+    if (!table)
+        return NULL;
+    if (column_n < 0)
+        column_n = table->CurrentColumn;
+    return TableGetColumnName(table, column_n);
+}
+
+// We allow querying for an extra column in order to poll the IsHovered state of the right-most section
+ImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    if (!table)
+        return ImGuiTableColumnFlags_None;
+    if (column_n < 0)
+        column_n = table->CurrentColumn;
+    if (column_n == table->ColumnsCount)
+        return (table->HoveredColumnBody == column_n) ? ImGuiTableColumnFlags_IsHovered : ImGuiTableColumnFlags_None;
+    return table->Columns[column_n].Flags;
+}
+
+void ImGui::TableSetColumnIsEnabled(int column_n, bool hidden)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    IM_ASSERT(table != NULL && table->IsLayoutLocked == false);
+    if (column_n < 0)
+        column_n = table->CurrentColumn;
+    IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
+    table->Columns[column_n].IsEnabledNextFrame = !hidden;
+}
+
+int ImGui::TableGetColumnIndex()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    if (!table)
+        return 0;
+    return table->CurrentColumn;
+}
+
+// Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows
+int ImGui::TableGetRowIndex()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    if (!table)
+        return 0;
+    return table->CurrentRow;
+}
+
+// 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.
+// - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right
+//   columns report a small offset so their CellBgRect can extend up to the outer border.
+ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n)
+{
+    const ImGuiTableColumn* column = &table->Columns[column_n];
+    float x1 = column->MinX;
+    float x2 = column->MaxX;
+    if (column->PrevEnabledColumn == -1)
+        x1 -= table->CellSpacingX1;
+    if (column->NextEnabledColumn == -1)
+        x2 += table->CellSpacingX2;
+    return ImRect(x1, table->RowPosY1, x2, table->RowPosY2);
+}
+
+const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n)
+{
+    const ImGuiTableColumn* column = &table->Columns[column_n];
+    if (column->NameOffset == -1)
+        return "";
+    return &table->ColumnsNames.Buf[column->NameOffset];
+}
+
+// Return the resizing ID for the right-side of the given column.
+ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no)
+{
+    IM_ASSERT(column_n < table->ColumnsCount);
+    ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n;
+    return id;
+}
+
+// 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)
+void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n)
+{
+    // Single auto width uses auto-fit
+    ImGuiTableColumn* column = &table->Columns[column_n];
+    if (!column->IsEnabled)
+        return;
+    column->CannotSkipItemsQueue = (1 << 0);
+    column->AutoFitQueue = (1 << 1);
+    if (column->Flags & ImGuiTableColumnFlags_WidthStretch)
+        table->AutoFitSingleStretchColumn = (ImGuiTableColumnIdx)column_n;
+}
+
+void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table)
+{
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        if (!column->IsEnabled)
+            continue;
+        column->CannotSkipItemsQueue = (1 << 0);
+        column->AutoFitQueue = (1 << 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;
+    ImGuiTable* table = g.CurrentTable;
+    if (!table)
+        return -1;
+    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->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0)
+            return;
+        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 = (ImGuiTableColumnIdx)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);
+    }
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Drawing
+//-------------------------------------------------------------------------
+// - TablePushBackgroundChannel() [Internal]
+// - TablePopBackgroundChannel() [Internal]
+// - TableSetupDrawChannels() [Internal]
+// - TableMergeDrawChannels() [Internal]
+// - TableDrawBorders() [Internal]
+//-------------------------------------------------------------------------
+
+void ImGui::TablePushBackgroundChannel()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    ImGuiTable* table = g.CurrentTable;
+
+    // Optimization: avoid SetCurrentChannel() + PushClipRect()
+    table->HostBackupClipRect = window->ClipRect;
+    SetWindowClipRectBeforeSetChannel(window, table->BgClipRectForDrawCmd);
+    table->DrawSplitter.SetCurrentChannel(window->DrawList, table->Bg1DrawChannelCurrent);
+}
+
+void ImGui::TablePopBackgroundChannel()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    ImGuiTable* table = g.CurrentTable;
+    ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
+
+    // Optimization: avoid PopClipRect() + SetCurrentChannel()
+    SetWindowClipRectBeforeSetChannel(window, table->HostBackupClipRect);
+    table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
+}
+
+// Allocate draw channels. Called by TableUpdateLayout()
+// - 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.
+// - We allocate 1 or 2 background draw channels. This is because we know PushTableBackground() is only used for
+//   horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4).
+// Draw channel allocation (before merging):
+// - NoClip                       --> 2+D+1 channels: bg0 + bg1 + foreground (same clip rect == 1 draw call) (FIXME-TABLE: could merge bg1 and foreground?)
+// - Clip                         --> 2+D+N channels
+// - FreezeRows                   --> 2+D+N*2 (unless scrolling value is zero)
+// - FreezeRows || FreezeColunns  --> 3+D+N*2 (unless scrolling value is zero)
+// Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0.
+void ImGui::TableSetupDrawChannels(ImGuiTable* table)
+{
+    const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1;
+    const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount;
+    const int channels_for_bg = 1 + 1 * freeze_row_multiplier;
+    const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0;
+    const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy;
+    table->DrawSplitter.Split(table->InnerWindow->DrawList, channels_total);
+    table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1);
+    table->Bg1DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG1_FROZEN;
+    table->Bg1DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG1_FROZEN);
+
+    int draw_channel_current = 2;
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        if (column->IsVisibleX && column->IsVisibleY)
+        {
+            column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current);
+            column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0));
+            if (!(table->Flags & ImGuiTableFlags_NoClip))
+                draw_channel_current++;
+        }
+        else
+        {
+            column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel;
+        }
+        column->DrawChannelCurrent = column->DrawChannelFrozen;
+    }
+}
+
+// This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable().
+// For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect,
+// actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels().
+//
+// 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      [ 01 ] row+col freeze
+//   [ .. ]  or no scroll   [ 2. ]  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 groups or not).
+// 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).
+//   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 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 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::TableMergeDrawChannels(ImGuiTable* table)
+{
+    ImGuiContext& g = *GImGui;
+    ImDrawListSplitter* splitter = &table->DrawSplitter;
+    const bool has_freeze_v = (table->FreezeRowsCount > 0);
+    const bool has_freeze_h = (table->FreezeColumnsCount > 0);
+
+    // Track which groups we are going to attempt to merge, and which channels goes into each group.
+    struct MergeGroup
+    {
+        ImRect  ClipRect;
+        int     ChannelsCount;
+        ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> ChannelsMask;
+    };
+    int merge_group_mask = 0x00;
+    MergeGroup merge_groups[4];
+    memset(merge_groups, 0, sizeof(merge_groups));
+
+    // 1. Scan channels and take note of those which can be merged
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0)
+            continue;
+        ImGuiTableColumn* column = &table->Columns[column_n];
+
+        const int merge_group_sub_count = has_freeze_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_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen;
+
+            // 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 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_NoClip))
+            {
+                float content_max_x;
+                if (!has_freeze_v)
+                    content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze
+                else if (merge_group_sub_n == 0)
+                    content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed);   // Row freeze: use width before freeze
+                else
+                    content_max_x = column->ContentMaxXUnfrozen;                                        // Row freeze: use width after freeze
+                if (content_max_x > column->ClipRect.Max.x)
+                    continue;
+            }
+
+            const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2);
+            IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS);
+            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_n);
+        }
+
+        // Invalidate current draw channel
+        // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data)
+        column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx)-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)
+    {
+        // We skip channel 0 (Bg0) and 1 (Bg1 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels().
+        const int LEADING_DRAW_CHANNELS = 2;
+        g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized
+        ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data;
+        ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> remaining_mask;                       // We need 132-bit of storage
+        remaining_mask.ClearBits();
+        remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count - 1);
+        remaining_mask.ClearBit(table->Bg1DrawChannelUnfrozen);
+        IM_ASSERT(has_freeze_v == false || table->Bg1DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG1_FROZEN);
+        int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS);
+        //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect;
+        ImRect host_rect = table->HostClipRect;
+        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_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 & 1) == 0 || !has_freeze_h)
+                    merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x);
+                if ((merge_group_n & 2) == 0 || !has_freeze_v)
+                    merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y);
+                if ((merge_group_n & 1) != 0)
+                    merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x);
+                if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0)
+                    merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.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];
+                for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++)
+                {
+                    // Copy + overwrite new clip rect
+                    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));
+                }
+            }
+
+            // Make sure Bg1DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0 and Bg1 frozen are fixed to 0 and 1)
+            if (merge_group_n == 1 && has_freeze_v)
+                memcpy(dst_tmp++, &splitter->_Channels[table->Bg1DrawChannelUnfrozen], sizeof(ImDrawChannel));
+        }
+
+        // Append unmergeable channels that we didn't reorder at the end of the list
+        for (int n = 0; n < splitter->_Count && remaining_count != 0; n++)
+        {
+            if (!remaining_mask.TestBit(n))
+                continue;
+            ImDrawChannel* channel = &splitter->_Channels[n];
+            memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));
+            remaining_count--;
+        }
+        IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size);
+        memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel));
+    }
+}
+
+// FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow)
+void ImGui::TableDrawBorders(ImGuiTable* table)
+{
+    ImGuiWindow* inner_window = table->InnerWindow;
+    ImGuiWindow* outer_window = table->OuterWindow;
+    table->DrawSplitter.SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_BG0);
+    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 border_size = TABLE_BORDER_SIZE;
+    const float draw_y1 = table->OuterRect.Min.y;
+    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)
+    {
+        for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
+        {
+            if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
+                continue;
+
+            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->InstanceCurrent);
+            const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0;
+
+            if (column->MaxX > table->InnerClipRect.Max.x && !is_resized)// && is_hovered)
+                continue;
+            if (column->NextEnabledColumn == -1 && !is_resizable)
+                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_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);
+        }
+    }
+
+    // Draw outer border
+    // FIXME-TABLE: could use AddRect or explicit VLine/HLine helper?
+    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;
+        const ImU32 outer_col = table->BorderColorStrong;
+        if (inner_window != outer_window) // FIXME-TABLE
+            outer_border.Expand(1.0f);
+        if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter)
+        {
+            outer_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, ~0, border_size);
+        }
+        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_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_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y)
+    {
+        // Draw bottom-most row border
+        const float border_y = table->RowPosY2;
+        if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y)
+            inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size);
+    }
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Sorting
+//-------------------------------------------------------------------------
+// - TableGetSortSpecs()
+// - TableFixColumnSortDirection() [Internal]
+// - TableSetColumnSortDirection() [Internal]
+// - TableSortSpecsSanitize() [Internal]
+// - TableSortSpecsBuild() [Internal]
+//-------------------------------------------------------------------------
+
+// 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()!
+ImGuiTableSortSpecs* ImGui::TableGetSortSpecs()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    IM_ASSERT(table != NULL);
+
+    if (!(table->Flags & ImGuiTableFlags_Sortable))
+        return NULL;
+
+    // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths.
+    if (!table->IsLayoutLocked)
+        TableUpdateLayout(table);
+
+    if (table->IsSortSpecsDirty)
+        TableSortSpecsBuild(table);
+
+    return table->SortSpecs.SpecsCount ? &table->SortSpecs : NULL;
+}
+
+void ImGui::TableFixColumnSortDirection(ImGuiTableColumn* column)
+{
+    // Initial sort state
+    if (column->SortDirection == ImGuiSortDirection_None)
+        column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImS8)(ImGuiSortDirection_Ascending);
+
+    // 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;
+}
+
+
+// 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(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+
+    if (!(table->Flags & ImGuiTableFlags_MultiSortable))
+        append_to_sort_specs = false;
+
+    ImGuiTableColumnIdx sort_order_max = 0;
+    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);
+
+    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* 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;
+}
+
+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;
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        if (column->SortOrder != -1 && !column->IsEnabled)
+            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 = (ImGuiTableColumnIdx)sort_n;
+
+            // 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;
+                break;
+            }
+        }
+    }
+
+    // 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];
+            if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort))
+            {
+                sort_order_count = 1;
+                column->SortOrder = 0;
+                TableFixColumnSortDirection(column);
+                break;
+            }
+        }
+
+    table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count;
+}
+
+void ImGui::TableSortSpecsBuild(ImGuiTable* table)
+{
+    IM_ASSERT(table->IsSortSpecsDirty);
+    TableSortSpecsSanitize(table);
+
+    // Write output
+    const bool single_sort_specs = (table->SortSpecsCount <= 1);
+    table->SortSpecsMulti.resize(single_sort_specs ? 0 : table->SortSpecsCount);
+    ImGuiTableColumnSortSpecs* sort_specs = single_sort_specs ? &table->SortSpecsSingle : table->SortSpecsMulti.Data;
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+    {
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        if (column->SortOrder == -1)
+            continue;
+        IM_ASSERT(column->SortOrder < table->SortSpecsCount);
+        ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder];
+        sort_spec->ColumnUserID = column->UserID;
+        sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n;
+        sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder;
+        sort_spec->SortDirection = column->SortDirection;
+    }
+    table->SortSpecs.Specs = sort_specs;
+    table->SortSpecs.SpecsCount = table->SortSpecsCount;
+    table->SortSpecs.SpecsDirty = true; // Mark as dirty for user
+    table->IsSortSpecsDirty = false; // Mark as not dirty for us
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Headers
+//-------------------------------------------------------------------------
+// - TableGetHeaderRowHeight() [Internal]
+// - TableHeadersRow()
+// - TableHeader()
+//-------------------------------------------------------------------------
+
+float ImGui::TableGetHeaderRowHeight()
+{
+    // Caring for a minor edge case:
+    // Calculate row height, for the unlikely case that some labels may be taller than others.
+    // If we didn't do that, uneven header height would highlight but smaller one before the tallest wouldn't catch input for all height.
+    // In your custom header row you may omit this all together and just call TableNextRow() without a height...
+    float row_height = GetTextLineHeight();
+    int columns_count = TableGetColumnCount();
+    for (int column_n = 0; column_n < columns_count; column_n++)
+        if (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_IsEnabled)
+            row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y);
+    row_height += GetStyle().CellPadding.y * 2.0f;
+    return row_height;
+}
+
+// [Public] 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.
+// See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this.
+// This code is constructed to not make much use of internal functions, as it is intended to be a template to copy.
+// FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public.
+void ImGui::TableHeadersRow()
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTable* table = g.CurrentTable;
+    IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!");
+
+    // Open row
+    const float row_y1 = GetCursorScreenPos().y;
+    const float row_height = TableGetHeaderRowHeight();
+    TableNextRow(ImGuiTableRowFlags_Headers, row_height);
+    if (table->HostSkipItems) // Merely an optimization, you may skip in your own code.
+        return;
+
+    const int columns_count = TableGetColumnCount();
+    for (int column_n = 0; column_n < columns_count; column_n++)
+    {
+        if (!TableSetColumnIndex(column_n))
+            continue;
+
+        // 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 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();
+    }
+
+    // Allow opening popup from the right-most section after the last column.
+    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(-1); // Will open a non-column-specific popup.
+}
+
+// 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: Style confusion between CellPadding.y and FramePadding.y
+void ImGui::TableHeader(const char* label)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (window->SkipItems)
+        return;
+
+    ImGuiTable* table = g.CurrentTable;
+    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];
+
+    // 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.
+    // FIXME-TABLE-PADDING: Problem if the correct outer-padding CellBgRect strays off our ClipRect
+    ImRect cell_r = TableGetCellBgRect(table, column_n);
+    float label_height = ImMax(label_size.y, table->RowMinHeight - table->CellPaddingY * 2.0f);
+
+    // Keep header highlighted when context menu is open.
+    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;
+
+    //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
+    //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
+
+    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);
+        TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn);
+        RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
+    }
+    if (held)
+        table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n;
+    window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f;
+
+    // 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 = (ImGuiTableColumnIdx)column_n;
+        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)
+            if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL)
+                if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder))
+                    if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < 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->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL)
+                if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder))
+                    if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest))
+                        table->ReorderColumnDir = +1;
+    }
+
+    // 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;
+        w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);// table->CellPadding.x);
+        if (column->SortOrder != -1)
+        {
+            char sort_order_suf[8];
+            w_sort_text = 0.0f;
+            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, cell_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_Up : ImGuiDir_Down, ARROW_SCALE);
+        }
+
+        // Handle clicking on column header to adjust Sort Order
+        if (pressed && table->ReorderColumn != column_n)
+        {
+            // 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(column_n, sort_direction, 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.
+    //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);
+
+    const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x);
+    if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay)
+        SetTooltip("%.*s", (int)(label_end - label), label);
+
+    // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging.
+    float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow;
+    column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, column->WorkMaxX);
+    column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x);
+
+    // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden
+    if (IsMouseReleased(1) && IsItemHovered())
+        TableOpenContextMenu(column_n);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Context Menu
+//-------------------------------------------------------------------------
+// - TableOpenContextMenu() [Internal]
+// - TableDrawContextMenu() [Internal]
+//-------------------------------------------------------------------------
+
+// Use -1 to open menu not specific to a given column.
+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))
+    {
+        table->IsContextPopupOpen = true;
+        table->ContextPopupColumn = (ImGuiTableColumnIdx)column_n;
+        table->InstanceInteracted = table->InstanceCurrent;
+        const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID);
+        OpenPopupEx(context_menu_id, ImGuiPopupFlags_None);
+    }
+}
+
+// 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)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiWindow* window = g.CurrentWindow;
+    if (window->SkipItems)
+        return;
+
+    bool want_separator = false;
+    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 (column != NULL)
+        {
+            const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled;
+            if (MenuItem("Size column to fit", NULL, false, can_resize))
+                TableSetColumnWidthAutoSingle(table, column_n);
+        }
+
+        const char* size_all_desc;
+        if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount)
+            size_all_desc = "Size all columns to fit###SizeAll";        // All fixed
+        else if (table->ColumnsEnabledFixedCount == 0)
+            size_all_desc = "Size all columns to default###SizeAll";    // All stretch
+        else
+            size_all_desc = "Size all columns to fit/default###SizeAll";// Mixed
+        if (MenuItem(size_all_desc, NULL))
+            TableSetColumnWidthAutoAll(table);
+        want_separator = true;
+    }
+
+    // Ordering
+    if (table->Flags & ImGuiTableFlags_Reorderable)
+    {
+        if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder))
+            table->IsResetDisplayOrderRequest = true;
+        want_separator = true;
+    }
+
+    // Reset all (should work but seems unnecessary/noisy to expose?)
+    //if (MenuItem("Reset all"))
+    //    table->IsResetAllRequest = true;
+
+    // Sorting
+    // (modify TableOpenContextMenu() to add _Sortable flag if enabling this)
+#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 = true;
+
+        PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true);
+        for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)
+        {
+            ImGuiTableColumn* other_column = &table->Columns[other_column_n];
+            const char* name = TableGetColumnName(table, other_column_n);
+            if (name == NULL || name[0] == 0)
+                name = "<Unknown>";
+
+            // Make sure we can't hide the last active column
+            bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true;
+            if (other_column->IsEnabled && table->ColumnsEnabledCount <= 1)
+                menu_item_active = false;
+            if (MenuItem(name, NULL, other_column->IsEnabled, menu_item_active))
+                other_column->IsEnabledNextFrame = !other_column->IsEnabled;
+        }
+        PopItemFlag();
+    }
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Settings (.ini data)
+//-------------------------------------------------------------------------
+// FIXME: The binding/finding/creating flow are too confusing.
+//-------------------------------------------------------------------------
+// - TableSettingsInit() [Internal]
+// - TableSettingsCalcChunkSize() [Internal]
+// - TableSettingsCreate() [Internal]
+// - TableSettingsFindByID() [Internal]
+// - TableGetBoundSettings() [Internal]
+// - TableResetSettings()
+// - TableSaveSettings() [Internal]
+// - TableLoadSettings() [Internal]
+// - TableSettingsHandler_ClearAll() [Internal]
+// - TableSettingsHandler_ApplyAll() [Internal]
+// - TableSettingsHandler_ReadOpen() [Internal]
+// - TableSettingsHandler_ReadLine() [Internal]
+// - TableSettingsHandler_WriteAll() [Internal]
+// - TableSettingsInstallHandler() [Internal]
+//-------------------------------------------------------------------------
+// [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.
+//-------------------------------------------------------------------------
+
+// Clear and initialize empty settings instance
+static void TableSettingsInit(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 = (ImGuiTableColumnIdx)columns_count;
+    settings->ColumnsCountMax = (ImGuiTableColumnIdx)columns_count_max;
+    settings->WantApply = true;
+}
+
+static size_t TableSettingsCalcChunkSize(int columns_count)
+{
+    return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings);
+}
+
+ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count)
+{
+    ImGuiContext& g = *GImGui;
+    ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count));
+    TableSettingsInit(settings, id, columns_count, columns_count);
+    return settings;
+}
+
+// Find existing settings
+ImGuiTableSettings* ImGui::TableSettingsFindByID(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;
+}
+
+// Get settings for a given table, NULL if none
+ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table)
+{
+    if (table->SettingsOffset != -1)
+    {
+        ImGuiContext& g = *GImGui;
+        ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset);
+        IM_ASSERT(settings->ID == table->ID);
+        if (settings->ColumnsCountMax >= table->ColumnsCount)
+            return settings; // OK
+        settings->ID = 0; // Invalidate storage, we won't fit because of a count change
+    }
+    return NULL;
+}
+
+// Restore initial state of table (with or without saved settings)
+void ImGui::TableResetSettings(ImGuiTable* table)
+{
+    table->IsInitializing = table->IsSettingsDirty = true;
+    table->IsResetAllRequest = false;
+    table->IsSettingsRequestLoad = false;                   // Don't reload from ini
+    table->SettingsLoadedFlags = ImGuiTableFlags_None;      // Mark as nothing loaded so our initialized data becomes authoritative
+}
+
+void ImGui::TableSaveSettings(ImGuiTable* table)
+{
+    table->IsSettingsDirty = false;
+    if (table->Flags & ImGuiTableFlags_NoSavedSettings)
+        return;
+
+    // Bind or create settings data
+    ImGuiContext& g = *GImGui;
+    ImGuiTableSettings* settings = TableGetBoundSettings(table);
+    if (settings == NULL)
+    {
+        settings = TableSettingsCreate(table->ID, table->ColumnsCount);
+        table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings);
+    }
+    settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount;
+
+    // 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;
+    ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
+
+    bool save_ref_scale = false;
+    settings->SaveFlags = ImGuiTableFlags_None;
+    for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++)
+    {
+        const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest;
+        column_settings->WidthOrWeight = width_or_weight;
+        column_settings->Index = (ImGuiTableColumnIdx)n;
+        column_settings->DisplayOrder = column->DisplayOrder;
+        column_settings->SortOrder = column->SortOrder;
+        column_settings->SortDirection = column->SortDirection;
+        column_settings->IsEnabled = column->IsEnabled;
+        column_settings->IsStretch = (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.
+        // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth 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->InitStretchWeightOrWidth)
+            settings->SaveFlags |= ImGuiTableFlags_Resizable;
+        if (column->DisplayOrder != n)
+            settings->SaveFlags |= ImGuiTableFlags_Reorderable;
+        if (column->SortOrder != -1)
+            settings->SaveFlags |= ImGuiTableFlags_Sortable;
+        if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0))
+            settings->SaveFlags |= ImGuiTableFlags_Hideable;
+    }
+    settings->SaveFlags &= table->Flags;
+    settings->RefScale = save_ref_scale ? table->RefScale : 0.0f;
+
+    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 = 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;
+
+    // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
+    ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
+    for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++)
+    {
+        int column_n = column_settings->Index;
+        if (column_n < 0 || column_n >= table->ColumnsCount)
+            continue;
+
+        ImGuiTableColumn* column = &table->Columns[column_n];
+        if (settings->SaveFlags & ImGuiTableFlags_Resizable)
+        {
+            if (column_settings->IsStretch)
+                column->StretchWeight = column_settings->WidthOrWeight;
+            else
+                column->WidthRequest = column_settings->WidthOrWeight;
+            column->AutoFitQueue = 0x00;
+        }
+        if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
+            column->DisplayOrder = column_settings->DisplayOrder;
+        else
+            column->DisplayOrder = (ImGuiTableColumnIdx)column_n;
+        column->IsEnabled = column->IsEnabledNextFrame = column_settings->IsEnabled;
+        column->SortOrder = column_settings->SortOrder;
+        column->SortDirection = column_settings->SortDirection;
+    }
+
+    // FIXME-TABLE: Need to validate .ini data
+    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
+        table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
+}
+
+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 = ImGui::TableSettingsFindByID(id))
+    {
+        if (settings->ColumnsCountMax >= columns_count)
+        {
+            TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle
+            return settings;
+        }
+        settings->ID = 0; // Invalidate storage, we won't fit because of a count change
+    }
+    return ImGui::TableSettingsCreate(id, columns_count);
+}
+
+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;
+    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)
+            return;
+        line = ImStrSkipBlank(line + r);
+        char c = 0;
+        ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n;
+        column->Index = (ImGuiTableColumnIdx)column_n;
+        if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; }
+        if (sscanf(line, "Width=%d%n", &n, &r) == 1)            { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; }
+        if (sscanf(line, "Weight=%f%n", &f, &r) == 1)           { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; }
+        if (sscanf(line, "Visible=%d%n", &n, &r) == 1)          { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; }
+        if (sscanf(line, "Order=%d%n", &n, &r) == 1)            { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; }
+        if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2)       { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; }
+    }
+}
+
+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))
+    {
+        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);
+        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++)
+        {
+            // "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->IsStretch)         buf->appendf(" Weight=%.4f", column->WidthOrWeight);
+            if (save_size && !column->IsStretch)        buf->appendf(" Width=%d", (int)column->WidthOrWeight);
+            if (save_visible)                           buf->appendf(" Visible=%d", column->IsEnabled);
+            if (save_order)                             buf->appendf(" Order=%d", column->DisplayOrder);
+            if (save_sort && column->SortOrder != -1)   buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^');
+            buf->append("\n");
+        }
+        buf->append("\n");
+    }
+}
+
+void ImGui::TableSettingsInstallHandler(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);
+}
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Garbage Collection
+//-------------------------------------------------------------------------
+// - TableRemove() [Internal]
+// - TableGcCompactTransientBuffers() [Internal]
+// - TableGcCompactSettings() [Internal]
+//-------------------------------------------------------------------------
+
+// Remove Table (currently only used by TestEngine)
+void ImGui::TableRemove(ImGuiTable* table)
+{
+    //IMGUI_DEBUG_LOG("TableRemove() id=0x%08X\n", table->ID);
+    ImGuiContext& g = *GImGui;
+    int table_idx = g.Tables.GetIndex(table);
+    //memset(table->RawData.Data, 0, table->RawData.size_in_bytes());
+    //memset(table, 0, sizeof(ImGuiTable));
+    g.Tables.Remove(table->ID, table);
+    g.TablesLastTimeActive[table_idx] = -1.0f;
+}
+
+// Free up/compact internal Table buffers for when it gets unused
+void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table)
+{
+    //IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID);
+    ImGuiContext& g = *GImGui;
+    IM_ASSERT(table->MemoryCompacted == false);
+    table->DrawSplitter.ClearFreeMemory();
+    table->SortSpecsMulti.clear();
+    table->SortSpecs.Specs = NULL;
+    table->IsSortSpecsDirty = true;
+    table->ColumnsNames.clear();
+    table->MemoryCompacted = true;
+    for (int n = 0; n < table->ColumnsCount; n++)
+        table->Columns[n].NameOffset = -1;
+    g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f;
+}
+
+// Compact and remove unused settings data (currently only used by TestEngine)
+void ImGui::TableGcCompactSettings()
+{
+    ImGuiContext& g = *GImGui;
+    int required_memory = 0;
+    for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
+        if (settings->ID != 0)
+            required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount);
+    if (required_memory == g.SettingsTables.Buf.Size)
+        return;
+    ImChunkStream<ImGuiTableSettings> new_chunk_stream;
+    new_chunk_stream.Buf.reserve(required_memory);
+    for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
+        if (settings->ID != 0)
+            memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount));
+    g.SettingsTables.swap(new_chunk_stream);
+}
+
+
+//-------------------------------------------------------------------------
+// [SECTION] Tables: Debugging
+//-------------------------------------------------------------------------
+// - DebugNodeTable() [Internal]
+//-------------------------------------------------------------------------
+
+#ifndef IMGUI_DISABLE_METRICS_WINDOW
+
+void ImGui::DebugNodeTable(ImGuiTable* table)
+{
+    char buf[512];
+    char* p = buf;
+    const char* buf_end = buf + IM_ARRAYSIZE(buf);
+    const bool is_active = (table->LastFrameActive >= ImGui::GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
+    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;
+    bool clear_settings = SmallButton("Clear settings");
+    BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f)", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight());
+    BulletText("ColumnsWidth: %.1f, AutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsTotalWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "");
+    BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX);
+    BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder);
+    BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn);
+    //BulletText("BgDrawChannels: %d/%d", 0, table->BgDrawChannelUnfrozen);
+    for (int n = 0; n < table->ColumnsCount; n++)
+    {
+        ImGuiTableColumn* column = &table->Columns[n];
+        const char* name = TableGetColumnName(table, n);
+        ImFormatString(buf, IM_ARRAYSIZE(buf),
+            "Column %d order %d name '%s': offset %+.2f to %+.2f\n"
+            "Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n"
+            "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f\n"
+            "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n"
+            "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n"
+            "Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s%s..",
+            n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x,
+            column->IsEnabled, column->IsVisibleX, column->IsVisibleY, column->IsRequestOutput, column->IsSkipItems, column->DrawChannelFrozen, column->DrawChannelUnfrozen,
+            column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight,
+            column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x,
+            column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX,
+            column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" : "", column->UserID, column->Flags,
+            (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "",
+            (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "",
+            (column->Flags & ImGuiTableColumnFlags_WidthAutoResize) ? "WidthAutoResize " : "",
+            (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : "");
+        Bullet();
+        Selectable(buf);
+        if (IsItemHovered())
+        {
+            ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y);
+            GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255));
+        }
+    }
+    if (ImGuiTableSettings* settings = TableGetBoundSettings(table))
+        DebugNodeTableSettings(settings);
+    if (clear_settings)
+        table->IsResetAllRequest = true;
+    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 Vis %d %s %7.3f UserID 0x%08X",
+            n, column_settings->DisplayOrder, column_settings->SortOrder,
+            (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---",
+            column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->UserID);
+    }
+    TreePop();
+}
+
+#else // #ifndef IMGUI_DISABLE_METRICS_WINDOW
+
+void ImGui::DebugNodeTable(ImGuiTable*) {}
+void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {}
+
+#endif
+
+
+//-------------------------------------------------------------------------
+// [SECTION] 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);
+}
+
+//-------------------------------------------------------------------------
+
+#endif // #ifndef IMGUI_DISABLE

+ 15 - 447
imgui_widgets.cpp

@@ -40,6 +40,7 @@ Index of this file:
 #endif
 #include "imgui_internal.h"
 
+// System includes
 #include <ctype.h>      // toupper
 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
 #include <stddef.h>     // intptr_t
@@ -47,6 +48,10 @@ Index of this file:
 #include <stdint.h>     // intptr_t
 #endif
 
+//-------------------------------------------------------------------------
+// Warnings
+//-------------------------------------------------------------------------
+
 // Visual Studio warnings
 #ifdef _MSC_VER
 #pragma warning (disable: 4127)     // condition expression is constant
@@ -72,9 +77,9 @@ Index of this file:
 #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 "-Wformat-nonliteral"        // warning: format not a string literal, format string not checked
-#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
+#pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
+#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
 
 //-------------------------------------------------------------------------
@@ -5953,7 +5958,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
     ImRect bb(min_x, pos.y, text_max.x, text_max.y);
     if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
     {
-        const float spacing_x = style.ItemSpacing.x;
+        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
         const float spacing_y = style.ItemSpacing.y;
         const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
         const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
@@ -5999,6 +6004,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 (span_all_columns && g.CurrentTable)
+        TablePushBackgroundChannel();
 
     // 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 +6054,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)
+        TablePopBackgroundChannel();
 
     if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
     RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
@@ -7678,7 +7687,7 @@ bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
     }
 
 #if 0
-    if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->ContentWidth)
+    if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
     {
         // Enlarge tab display when hovering
         bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
@@ -7720,7 +7729,7 @@ bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
 
     // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
     // We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar (which g.HoveredId ignores)
-    if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f && IsItemHovered())
+    if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered())
         if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
             SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
 
@@ -7866,445 +7875,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

+ 10 - 0
misc/natvis/imgui.natvis

@@ -13,6 +13,16 @@
     </ArrayItems>
   </Expand>
 </Type>
+  
+<Type Name="ImSpan&lt;*&gt;">
+  <DisplayString>{{Size={DataEnd-Data} }}</DisplayString>
+  <Expand>
+    <ArrayItems>
+      <Size>DataEnd-Data</Size>
+      <ValuePointer>Data</ValuePointer>
+    </ArrayItems>
+  </Expand>
+</Type>
 
 <Type Name="ImVec2">
   <DisplayString>{{x={x,g} y={y,g}}}</DisplayString>

+ 1 - 0
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

Some files were not shown because too many files changed in this diff