imgui

FORK: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
git clone https://git.neptards.moe/neptards/imgui.git
Log | Files | Refs

imgui_tables.cpp (212857B)


      1 // dear imgui, v1.83
      2 // (tables and columns code)
      3 
      4 /*
      5 
      6 Index of this file:
      7 
      8 // [SECTION] Commentary
      9 // [SECTION] Header mess
     10 // [SECTION] Tables: Main code
     11 // [SECTION] Tables: Simple accessors
     12 // [SECTION] Tables: Row changes
     13 // [SECTION] Tables: Columns changes
     14 // [SECTION] Tables: Columns width management
     15 // [SECTION] Tables: Drawing
     16 // [SECTION] Tables: Sorting
     17 // [SECTION] Tables: Headers
     18 // [SECTION] Tables: Context Menu
     19 // [SECTION] Tables: Settings (.ini data)
     20 // [SECTION] Tables: Garbage Collection
     21 // [SECTION] Tables: Debugging
     22 // [SECTION] Columns, BeginColumns, EndColumns, etc.
     23 
     24 */
     25 
     26 // Navigating this file:
     27 // - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot.
     28 // - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments.
     29 
     30 //-----------------------------------------------------------------------------
     31 // [SECTION] Commentary
     32 //-----------------------------------------------------------------------------
     33 
     34 //-----------------------------------------------------------------------------
     35 // Typical tables call flow: (root level is generally public API):
     36 //-----------------------------------------------------------------------------
     37 // - BeginTable()                               user begin into a table
     38 //    | BeginChild()                            - (if ScrollX/ScrollY is set)
     39 //    | TableBeginInitMemory()                  - first time table is used
     40 //    | TableResetSettings()                    - on settings reset
     41 //    | TableLoadSettings()                     - on settings load
     42 //    | TableBeginApplyRequests()               - apply queued resizing/reordering/hiding requests
     43 //    | - TableSetColumnWidth()                 - apply resizing width (for mouse resize, often requested by previous frame)
     44 //    |    - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width
     45 // - TableSetupColumn()                         user submit columns details (optional)
     46 // - TableSetupScrollFreeze()                   user submit scroll freeze information (optional)
     47 //-----------------------------------------------------------------------------
     48 // - TableUpdateLayout() [Internal]             followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow().
     49 //    | TableSetupDrawChannels()                - setup ImDrawList channels
     50 //    | TableUpdateBorders()                    - detect hovering columns for resize, ahead of contents submission
     51 //    | TableDrawContextMenu()                  - draw right-click context menu
     52 //-----------------------------------------------------------------------------
     53 // - TableHeadersRow() or TableHeader()         user submit a headers row (optional)
     54 //    | TableSortSpecsClickColumn()             - when left-clicked: alter sort order and sort direction
     55 //    | TableOpenContextMenu()                  - when right-clicked: trigger opening of the default context menu
     56 // - TableGetSortSpecs()                        user queries updated sort specs (optional, generally after submitting headers)
     57 // - TableNextRow()                             user begin into a new row (also automatically called by TableHeadersRow())
     58 //    | TableEndRow()                           - finish existing row
     59 //    | TableBeginRow()                         - add a new row
     60 // - TableSetColumnIndex() / TableNextColumn()  user begin into a cell
     61 //    | TableEndCell()                          - close existing column/cell
     62 //    | TableBeginCell()                        - enter into current column/cell
     63 // - [...]                                      user emit contents
     64 //-----------------------------------------------------------------------------
     65 // - EndTable()                                 user ends the table
     66 //    | TableDrawBorders()                      - draw outer borders, inner vertical borders
     67 //    | TableMergeDrawChannels()                - merge draw channels if clipping isn't required
     68 //    | EndChild()                              - (if ScrollX/ScrollY is set)
     69 //-----------------------------------------------------------------------------
     70 
     71 //-----------------------------------------------------------------------------
     72 // TABLE SIZING
     73 //-----------------------------------------------------------------------------
     74 // (Read carefully because this is subtle but it does make sense!)
     75 //-----------------------------------------------------------------------------
     76 // About 'outer_size':
     77 // Its meaning needs to differ slightly depending on if we are using ScrollX/ScrollY flags.
     78 // Default value is ImVec2(0.0f, 0.0f).
     79 //   X
     80 //   - outer_size.x <= 0.0f  ->  Right-align from window/work-rect right-most edge. With -FLT_MIN or 0.0f will align exactly on right-most edge.
     81 //   - outer_size.x  > 0.0f  ->  Set Fixed width.
     82 //   Y with ScrollX/ScrollY disabled: we output table directly in current window
     83 //   - outer_size.y  < 0.0f  ->  Bottom-align (but will auto extend, unless _NoHostExtendY is set). Not meaningful is parent window can vertically scroll.
     84 //   - outer_size.y  = 0.0f  ->  No minimum height (but will auto extend, unless _NoHostExtendY is set)
     85 //   - outer_size.y  > 0.0f  ->  Set Minimum height (but will auto extend, unless _NoHostExtenY is set)
     86 //   Y with ScrollX/ScrollY enabled: using a child window for scrolling
     87 //   - outer_size.y  < 0.0f  ->  Bottom-align. Not meaningful is parent window can vertically scroll.
     88 //   - outer_size.y  = 0.0f  ->  Bottom-align, consistent with BeginChild(). Not recommended unless table is last item in parent window.
     89 //   - outer_size.y  > 0.0f  ->  Set Exact height. Recommended when using Scrolling on any axis.
     90 //-----------------------------------------------------------------------------
     91 // Outer size is also affected by the NoHostExtendX/NoHostExtendY flags.
     92 // Important to that note how the two flags have slightly different behaviors!
     93 //   - ImGuiTableFlags_NoHostExtendX -> Make outer width auto-fit to columns (overriding outer_size.x value). Only available when ScrollX/ScrollY are disabled and Stretch columns are not used.
     94 //   - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY is disabled. Data below the limit will be clipped and not visible.
     95 // In theory ImGuiTableFlags_NoHostExtendY could be the default and any non-scrolling tables with outer_size.y != 0.0f would use exact height.
     96 // This would be consistent but perhaps less useful and more confusing (as vertically clipped items are not easily noticeable)
     97 //-----------------------------------------------------------------------------
     98 // About 'inner_width':
     99 //   With ScrollX disabled:
    100 //   - inner_width          ->  *ignored*
    101 //   With ScrollX enabled:
    102 //   - inner_width  < 0.0f  ->  *illegal* fit in known width (right align from outer_size.x) <-- weird
    103 //   - 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.
    104 //   - 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!
    105 //-----------------------------------------------------------------------------
    106 // Details:
    107 // - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept
    108 //   of "available space" doesn't make sense.
    109 // - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding
    110 //   of what the value does.
    111 //-----------------------------------------------------------------------------
    112 
    113 //-----------------------------------------------------------------------------
    114 // COLUMNS SIZING POLICIES
    115 //-----------------------------------------------------------------------------
    116 // About overriding column sizing policy and width/weight with TableSetupColumn():
    117 // We use a default parameter of 'init_width_or_weight == -1'.
    118 //   - with ImGuiTableColumnFlags_WidthFixed,    init_width  <= 0 (default)  --> width is automatic
    119 //   - with ImGuiTableColumnFlags_WidthFixed,    init_width  >  0 (explicit) --> width is custom
    120 //   - with ImGuiTableColumnFlags_WidthStretch,  init_weight <= 0 (default)  --> weight is 1.0f
    121 //   - with ImGuiTableColumnFlags_WidthStretch,  init_weight >  0 (explicit) --> weight is custom
    122 // Widths are specified _without_ CellPadding. If you specify a width of 100.0f, the column will be cover (100.0f + Padding * 2.0f)
    123 // and you can fit a 100.0f wide item in it without clipping and with full padding.
    124 //-----------------------------------------------------------------------------
    125 // About default sizing policy (if you don't specify a ImGuiTableColumnFlags_WidthXXXX flag)
    126 //   - with Table policy ImGuiTableFlags_SizingFixedFit      --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is equal to contents width
    127 //   - with Table policy ImGuiTableFlags_SizingFixedSame     --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is max of all contents width
    128 //   - with Table policy ImGuiTableFlags_SizingStretchSame   --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is 1.0f
    129 //   - with Table policy ImGuiTableFlags_SizingStretchWeight --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is proportional to contents
    130 // Default Width and default Weight can be overridden when calling TableSetupColumn().
    131 //-----------------------------------------------------------------------------
    132 // About mixing Fixed/Auto and Stretch columns together:
    133 //   - the typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns.
    134 //   - using mixed policies with ScrollX does not make much sense, as using Stretch columns with ScrollX does not make much sense in the first place!
    135 //     that is, unless 'inner_width' is passed to BeginTable() to explicitly provide a total width to layout columns in.
    136 //   - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the width of the maximum contents.
    137 //   - when using ImGuiTableFlags_SizingStretchSame with mixed columns, only the Stretch columns will match their weight/widths.
    138 //-----------------------------------------------------------------------------
    139 // About using column width:
    140 // If a column is manual resizable or has a width specified with TableSetupColumn():
    141 //   - you may use GetContentRegionAvail().x to query the width available in a given column.
    142 //   - right-side alignment features such as SetNextItemWidth(-x) or PushItemWidth(-x) will rely on this width.
    143 // If the column is not resizable and has no width specified with TableSetupColumn():
    144 //   - its width will be automatic and be set to the max of items submitted.
    145 //   - therefore you generally cannot have ALL items of the columns use e.g. SetNextItemWidth(-FLT_MIN).
    146 //   - but if the column has one or more items of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN).
    147 //-----------------------------------------------------------------------------
    148 
    149 
    150 //-----------------------------------------------------------------------------
    151 // TABLES CLIPPING/CULLING
    152 //-----------------------------------------------------------------------------
    153 // About clipping/culling of Rows in Tables:
    154 // - For large numbers of rows, it is recommended you use ImGuiListClipper to only submit visible rows.
    155 //   ImGuiListClipper is reliant on the fact that rows are of equal height.
    156 //   See 'Demo->Tables->Vertical Scrolling' or 'Demo->Tables->Advanced' for a demo of using the clipper.
    157 // - Note that auto-resizing columns don't play well with using the clipper.
    158 //   By default a table with _ScrollX but without _Resizable will have column auto-resize.
    159 //   So, if you want to use the clipper, make sure to either enable _Resizable, either setup columns width explicitly with _WidthFixed.
    160 //-----------------------------------------------------------------------------
    161 // About clipping/culling of Columns in Tables:
    162 // - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing
    163 //   width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know
    164 //   it is not going to contribute to row height.
    165 //   In many situations, you may skip submitting contents for every column but one (e.g. the first one).
    166 // - Case A: column is not hidden by user, and at least partially in sight (most common case).
    167 // - 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.
    168 // - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.).
    169 //
    170 //                        [A]         [B]          [C]
    171 //  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.
    172 //          SkipItems:    false       false        true        -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no layout output.
    173 //           ClipRect:    normal      zero-width   zero-width  -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out mid-way.
    174 //  ImDrawList output:    normal      dummy        dummy       -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted (because cliprect is zero-width anyway).
    175 //
    176 // - We need to distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row.
    177 //   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.
    178 //-----------------------------------------------------------------------------
    179 // About clipping/culling of whole Tables:
    180 // - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false.
    181 //-----------------------------------------------------------------------------
    182 
    183 //-----------------------------------------------------------------------------
    184 // [SECTION] Header mess
    185 //-----------------------------------------------------------------------------
    186 
    187 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
    188 #define _CRT_SECURE_NO_WARNINGS
    189 #endif
    190 
    191 #include "imgui.h"
    192 #ifndef IMGUI_DISABLE
    193 
    194 #ifndef IMGUI_DEFINE_MATH_OPERATORS
    195 #define IMGUI_DEFINE_MATH_OPERATORS
    196 #endif
    197 #include "imgui_internal.h"
    198 
    199 // System includes
    200 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
    201 #include <stddef.h>     // intptr_t
    202 #else
    203 #include <stdint.h>     // intptr_t
    204 #endif
    205 
    206 // Visual Studio warnings
    207 #ifdef _MSC_VER
    208 #pragma warning (disable: 4127)     // condition expression is constant
    209 #pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
    210 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
    211 #pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types
    212 #endif
    213 #pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
    214 #pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
    215 #endif
    216 
    217 // Clang/GCC warnings with -Weverything
    218 #if defined(__clang__)
    219 #if __has_warning("-Wunknown-warning-option")
    220 #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!
    221 #endif
    222 #pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
    223 #pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
    224 #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.
    225 #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.
    226 #pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
    227 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
    228 #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.
    229 #pragma clang diagnostic ignored "-Wenum-enum-conversion"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
    230 #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
    231 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
    232 #elif defined(__GNUC__)
    233 #pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
    234 #pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
    235 #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
    236 #endif
    237 
    238 //-----------------------------------------------------------------------------
    239 // [SECTION] Tables: Main code
    240 //-----------------------------------------------------------------------------
    241 // - TableFixFlags() [Internal]
    242 // - TableFindByID() [Internal]
    243 // - BeginTable()
    244 // - BeginTableEx() [Internal]
    245 // - TableBeginInitMemory() [Internal]
    246 // - TableBeginApplyRequests() [Internal]
    247 // - TableSetupColumnFlags() [Internal]
    248 // - TableUpdateLayout() [Internal]
    249 // - TableUpdateBorders() [Internal]
    250 // - EndTable()
    251 // - TableSetupColumn()
    252 // - TableSetupScrollFreeze()
    253 //-----------------------------------------------------------------------------
    254 
    255 // Configuration
    256 static const int TABLE_DRAW_CHANNEL_BG0 = 0;
    257 static const int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1;
    258 static const int TABLE_DRAW_CHANNEL_NOCLIP = 2;                     // When using ImGuiTableFlags_NoClip (this becomes the last visible channel)
    259 static const float TABLE_BORDER_SIZE                     = 1.0f;    // FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering.
    260 static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f;    // Extend outside inner borders.
    261 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.
    262 
    263 // Helper
    264 inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window)
    265 {
    266     // Adjust flags: set default sizing policy
    267     if ((flags & ImGuiTableFlags_SizingMask_) == 0)
    268         flags |= ((flags & ImGuiTableFlags_ScrollX) || (outer_window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) ? ImGuiTableFlags_SizingFixedFit : ImGuiTableFlags_SizingStretchSame;
    269 
    270     // Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame
    271     if ((flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)
    272         flags |= ImGuiTableFlags_NoKeepColumnsVisible;
    273 
    274     // Adjust flags: enforce borders when resizable
    275     if (flags & ImGuiTableFlags_Resizable)
    276         flags |= ImGuiTableFlags_BordersInnerV;
    277 
    278     // Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on
    279     if (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY))
    280         flags &= ~(ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY);
    281 
    282     // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody
    283     if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize)
    284         flags &= ~ImGuiTableFlags_NoBordersInBody;
    285 
    286     // Adjust flags: disable saved settings if there's nothing to save
    287     if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0)
    288         flags |= ImGuiTableFlags_NoSavedSettings;
    289 
    290     // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set)
    291 #ifdef IMGUI_HAS_DOCK
    292     ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop;
    293 #else
    294     ImGuiWindow* window_for_settings = outer_window->RootWindow;
    295 #endif
    296     if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings)
    297         flags |= ImGuiTableFlags_NoSavedSettings;
    298 
    299     return flags;
    300 }
    301 
    302 ImGuiTable* ImGui::TableFindByID(ImGuiID id)
    303 {
    304     ImGuiContext& g = *GImGui;
    305     return g.Tables.GetByKey(id);
    306 }
    307 
    308 // Read about "TABLE SIZING" at the top of this file.
    309 bool    ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width)
    310 {
    311     ImGuiID id = GetID(str_id);
    312     return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width);
    313 }
    314 
    315 bool    ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width)
    316 {
    317     ImGuiContext& g = *GImGui;
    318     ImGuiWindow* outer_window = GetCurrentWindow();
    319     if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible.
    320         return false;
    321 
    322     // Sanity checks
    323     IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!");
    324     if (flags & ImGuiTableFlags_ScrollX)
    325         IM_ASSERT(inner_width >= 0.0f);
    326 
    327     // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve.
    328     const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0;
    329     const ImVec2 avail_size = GetContentRegionAvail();
    330     ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f);
    331     ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size);
    332     if (use_child_window && IsClippedEx(outer_rect, 0, false))
    333     {
    334         ItemSize(outer_rect);
    335         return false;
    336     }
    337 
    338     // Acquire storage for the table
    339     ImGuiTable* table = g.Tables.GetOrAddByKey(id);
    340     const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1;
    341     const ImGuiID instance_id = id + instance_no;
    342     const ImGuiTableFlags table_last_flags = table->Flags;
    343     if (instance_no > 0)
    344         IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID");
    345 
    346     // Acquire temporary buffers
    347     const int table_idx = g.Tables.GetIndex(table);
    348     g.CurrentTableStackIdx++;
    349     if (g.CurrentTableStackIdx + 1 > g.TablesTempDataStack.Size)
    350         g.TablesTempDataStack.resize(g.CurrentTableStackIdx + 1, ImGuiTableTempData());
    351     ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempDataStack[g.CurrentTableStackIdx];
    352     temp_data->TableIndex = table_idx;
    353     table->DrawSplitter = &table->TempData->DrawSplitter;
    354     table->DrawSplitter->Clear();
    355 
    356     // Fix flags
    357     table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0;
    358     flags = TableFixFlags(flags, outer_window);
    359 
    360     // Initialize
    361     table->ID = id;
    362     table->Flags = flags;
    363     table->InstanceCurrent = (ImS16)instance_no;
    364     table->LastFrameActive = g.FrameCount;
    365     table->OuterWindow = table->InnerWindow = outer_window;
    366     table->ColumnsCount = columns_count;
    367     table->IsLayoutLocked = false;
    368     table->InnerWidth = inner_width;
    369     temp_data->UserOuterSize = outer_size;
    370 
    371     // When not using a child window, WorkRect.Max will grow as we append contents.
    372     if (use_child_window)
    373     {
    374         // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent
    375         // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing)
    376         ImVec2 override_content_size(FLT_MAX, FLT_MAX);
    377         if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY))
    378             override_content_size.y = FLT_MIN;
    379 
    380         // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and
    381         // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align
    382         // based on the right side of the child window work rect, which would require knowing ahead if we are going to
    383         // have decoration taking horizontal spaces (typically a vertical scrollbar).
    384         if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f)
    385             override_content_size.x = inner_width;
    386 
    387         if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX)
    388             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));
    389 
    390         // Reset scroll if we are reactivating it
    391         if ((table_last_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0)
    392             SetNextWindowScroll(ImVec2(0.0f, 0.0f));
    393 
    394         // Create scrolling region (without border and zero window padding)
    395         ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None;
    396         BeginChildEx(name, instance_id, outer_rect.GetSize(), false, child_flags);
    397         table->InnerWindow = g.CurrentWindow;
    398         table->WorkRect = table->InnerWindow->WorkRect;
    399         table->OuterRect = table->InnerWindow->Rect();
    400         table->InnerRect = table->InnerWindow->InnerRect;
    401         IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f);
    402     }
    403     else
    404     {
    405         // For non-scrolling tables, WorkRect == OuterRect == InnerRect.
    406         // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable().
    407         table->WorkRect = table->OuterRect = table->InnerRect = outer_rect;
    408     }
    409 
    410     // Push a standardized ID for both child-using and not-child-using tables
    411     PushOverrideID(instance_id);
    412 
    413     // Backup a copy of host window members we will modify
    414     ImGuiWindow* inner_window = table->InnerWindow;
    415     table->HostIndentX = inner_window->DC.Indent.x;
    416     table->HostClipRect = inner_window->ClipRect;
    417     table->HostSkipItems = inner_window->SkipItems;
    418     temp_data->HostBackupWorkRect = inner_window->WorkRect;
    419     temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect;
    420     temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset;
    421     temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize;
    422     temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize;
    423     temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos;
    424     temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth;
    425     temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size;
    426     inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
    427 
    428     // Padding and Spacing
    429     // - None               ........Content..... Pad .....Content........
    430     // - PadOuter           | Pad ..Content..... Pad .....Content.. Pad |
    431     // - PadInner           ........Content.. Pad | Pad ..Content........
    432     // - PadOuter+PadInner  | Pad ..Content.. Pad | Pad ..Content.. Pad |
    433     const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0;
    434     const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true;
    435     const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f;
    436     const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f;
    437     const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f;
    438     table->CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border;
    439     table->CellSpacingX2 = inner_spacing_explicit;
    440     table->CellPaddingX = inner_padding_explicit;
    441     table->CellPaddingY = g.Style.CellPadding.y;
    442 
    443     const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
    444     const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f;
    445     table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX;
    446 
    447     table->CurrentColumn = -1;
    448     table->CurrentRow = -1;
    449     table->RowBgColorCounter = 0;
    450     table->LastRowFlags = ImGuiTableRowFlags_None;
    451     table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect;
    452     table->InnerClipRect.ClipWith(table->WorkRect);     // We need this to honor inner_width
    453     table->InnerClipRect.ClipWithFull(table->HostClipRect);
    454     table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y;
    455 
    456     table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow
    457     table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow()
    458     table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any
    459     table->FreezeColumnsRequest = table->FreezeColumnsCount = 0;
    460     table->IsUnfrozenRows = true;
    461     table->DeclColumnsCount = 0;
    462 
    463     // Using opaque colors facilitate overlapping elements of the grid
    464     table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong);
    465     table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight);
    466 
    467     // Make table current
    468     g.CurrentTable = table;
    469     outer_window->DC.CurrentTableIdx = table_idx;
    470     if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly.
    471         inner_window->DC.CurrentTableIdx = table_idx;
    472 
    473     if ((table_last_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0)
    474         table->IsResetDisplayOrderRequest = true;
    475 
    476     // Mark as used
    477     if (table_idx >= g.TablesLastTimeActive.Size)
    478         g.TablesLastTimeActive.resize(table_idx + 1, -1.0f);
    479     g.TablesLastTimeActive[table_idx] = (float)g.Time;
    480     temp_data->LastTimeActive = (float)g.Time;
    481     table->MemoryCompacted = false;
    482 
    483     // Setup memory buffer (clear data if columns count changed)
    484     ImGuiTableColumn* old_columns_to_preserve = NULL;
    485     void* old_columns_raw_data = NULL;
    486     const int old_columns_count = table->Columns.size();
    487     if (old_columns_count != 0 && old_columns_count != columns_count)
    488     {
    489         // Attempt to preserve width on column count change (#4046)
    490         old_columns_to_preserve = table->Columns.Data;
    491         old_columns_raw_data = table->RawData;
    492         table->RawData = NULL;
    493     }
    494     if (table->RawData == NULL)
    495     {
    496         TableBeginInitMemory(table, columns_count);
    497         table->IsInitializing = table->IsSettingsRequestLoad = true;
    498     }
    499     if (table->IsResetAllRequest)
    500         TableResetSettings(table);
    501     if (table->IsInitializing)
    502     {
    503         // Initialize
    504         table->SettingsOffset = -1;
    505         table->IsSortSpecsDirty = true;
    506         table->InstanceInteracted = -1;
    507         table->ContextPopupColumn = -1;
    508         table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1;
    509         table->AutoFitSingleColumn = -1;
    510         table->HoveredColumnBody = table->HoveredColumnBorder = -1;
    511         for (int n = 0; n < columns_count; n++)
    512         {
    513             ImGuiTableColumn* column = &table->Columns[n];
    514             if (old_columns_to_preserve && n < old_columns_count)
    515             {
    516                 // FIXME: We don't attempt to preserve column order in this path.
    517                 *column = old_columns_to_preserve[n];
    518             }
    519             else
    520             {
    521                 float width_auto = column->WidthAuto;
    522                 *column = ImGuiTableColumn();
    523                 column->WidthAuto = width_auto;
    524                 column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker
    525                 column->IsEnabled = column->IsEnabledNextFrame = true;
    526             }
    527             column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n;
    528         }
    529     }
    530     if (old_columns_raw_data)
    531         IM_FREE(old_columns_raw_data);
    532 
    533     // Load settings
    534     if (table->IsSettingsRequestLoad)
    535         TableLoadSettings(table);
    536 
    537     // Handle DPI/font resize
    538     // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well.
    539     // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition.
    540     // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory.
    541     // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path.
    542     const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ?
    543     if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit)
    544     {
    545         const float scale_factor = new_ref_scale_unit / table->RefScale;
    546         //IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor);
    547         for (int n = 0; n < columns_count; n++)
    548             table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor;
    549     }
    550     table->RefScale = new_ref_scale_unit;
    551 
    552     // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call..
    553     // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user.
    554     // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option.
    555     inner_window->SkipItems = true;
    556 
    557     // Clear names
    558     // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn()
    559     if (table->ColumnsNames.Buf.Size > 0)
    560         table->ColumnsNames.Buf.resize(0);
    561 
    562     // Apply queued resizing/reordering/hiding requests
    563     TableBeginApplyRequests(table);
    564 
    565     return true;
    566 }
    567 
    568 // For reference, the average total _allocation count_ for a table is:
    569 // + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables)
    570 // + 1 (for table->RawData allocated below)
    571 // + 1 (for table->ColumnsNames, if names are used)
    572 // + 1 (for table->Splitter._Channels)
    573 // + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels)
    574 // Where active_channels_count is variable but often == columns_count or columns_count + 1, see TableSetupDrawChannels() for details.
    575 // Unused channels don't perform their +2 allocations.
    576 void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count)
    577 {
    578     // Allocate single buffer for our arrays
    579     ImSpanAllocator<3> span_allocator;
    580     span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn));
    581     span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx));
    582     span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4);
    583     table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes());
    584     memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes());
    585     span_allocator.SetArenaBasePtr(table->RawData);
    586     span_allocator.GetSpan(0, &table->Columns);
    587     span_allocator.GetSpan(1, &table->DisplayOrderToIndex);
    588     span_allocator.GetSpan(2, &table->RowCellData);
    589 }
    590 
    591 // Apply queued resizing/reordering/hiding requests
    592 void ImGui::TableBeginApplyRequests(ImGuiTable* table)
    593 {
    594     // Handle resizing request
    595     // (We process this at the first TableBegin of the frame)
    596     // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling?
    597     if (table->InstanceCurrent == 0)
    598     {
    599         if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX)
    600             TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth);
    601         table->LastResizedColumn = table->ResizedColumn;
    602         table->ResizedColumnNextWidth = FLT_MAX;
    603         table->ResizedColumn = -1;
    604 
    605         // Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy.
    606         // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings.
    607         if (table->AutoFitSingleColumn != -1)
    608         {
    609             TableSetColumnWidth(table->AutoFitSingleColumn, table->Columns[table->AutoFitSingleColumn].WidthAuto);
    610             table->AutoFitSingleColumn = -1;
    611         }
    612     }
    613 
    614     // Handle reordering request
    615     // Note: we don't clear ReorderColumn after handling the request.
    616     if (table->InstanceCurrent == 0)
    617     {
    618         if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1)
    619             table->ReorderColumn = -1;
    620         table->HeldHeaderColumn = -1;
    621         if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0)
    622         {
    623             // We need to handle reordering across hidden columns.
    624             // In the configuration below, moving C to the right of E will lead to:
    625             //    ... C [D] E  --->  ... [D] E  C   (Column name/index)
    626             //    ... 2  3  4        ...  2  3  4   (Display order)
    627             const int reorder_dir = table->ReorderColumnDir;
    628             IM_ASSERT(reorder_dir == -1 || reorder_dir == +1);
    629             IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable);
    630             ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn];
    631             ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn];
    632             IM_UNUSED(dst_column);
    633             const int src_order = src_column->DisplayOrder;
    634             const int dst_order = dst_column->DisplayOrder;
    635             src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order;
    636             for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir)
    637                 table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir;
    638             IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir);
    639 
    640             // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[],
    641             // rebuild the later from the former.
    642             for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
    643                 table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
    644             table->ReorderColumnDir = 0;
    645             table->IsSettingsDirty = true;
    646         }
    647     }
    648 
    649     // Handle display order reset request
    650     if (table->IsResetDisplayOrderRequest)
    651     {
    652         for (int n = 0; n < table->ColumnsCount; n++)
    653             table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n;
    654         table->IsResetDisplayOrderRequest = false;
    655         table->IsSettingsDirty = true;
    656     }
    657 }
    658 
    659 // Adjust flags: default width mode + stretch columns are not allowed when auto extending
    660 static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in)
    661 {
    662     ImGuiTableColumnFlags flags = flags_in;
    663 
    664     // Sizing Policy
    665     if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0)
    666     {
    667         const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_);
    668         if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame)
    669             flags |= ImGuiTableColumnFlags_WidthFixed;
    670         else
    671             flags |= ImGuiTableColumnFlags_WidthStretch;
    672     }
    673     else
    674     {
    675         IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used.
    676     }
    677 
    678     // Resize
    679     if ((table->Flags & ImGuiTableFlags_Resizable) == 0)
    680         flags |= ImGuiTableColumnFlags_NoResize;
    681 
    682     // Sorting
    683     if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending))
    684         flags |= ImGuiTableColumnFlags_NoSort;
    685 
    686     // Indentation
    687     if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0)
    688         flags |= (table->Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable;
    689 
    690     // Alignment
    691     //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0)
    692     //    flags |= ImGuiTableColumnFlags_AlignCenter;
    693     //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used.
    694 
    695     // Preserve status flags
    696     column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_);
    697 
    698     // Build an ordered list of available sort directions
    699     column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0;
    700     if (table->Flags & ImGuiTableFlags_Sortable)
    701     {
    702         int count = 0, mask = 0, list = 0;
    703         if ((flags & ImGuiTableColumnFlags_PreferSortAscending)  != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending)  == 0) { mask |= 1 << ImGuiSortDirection_Ascending;  list |= ImGuiSortDirection_Ascending  << (count << 1); count++; }
    704         if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; }
    705         if ((flags & ImGuiTableColumnFlags_PreferSortAscending)  == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending)  == 0) { mask |= 1 << ImGuiSortDirection_Ascending;  list |= ImGuiSortDirection_Ascending  << (count << 1); count++; }
    706         if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; }
    707         if ((table->Flags & ImGuiTableFlags_SortTristate) || count == 0) { mask |= 1 << ImGuiSortDirection_None; count++; }
    708         column->SortDirectionsAvailList = (ImU8)list;
    709         column->SortDirectionsAvailMask = (ImU8)mask;
    710         column->SortDirectionsAvailCount = (ImU8)count;
    711         ImGui::TableFixColumnSortDirection(table, column);
    712     }
    713 }
    714 
    715 // Layout columns for the frame. This is in essence the followup to BeginTable().
    716 // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first.
    717 // FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for _WidthAuto columns.
    718 // Increase feedback side-effect with widgets relying on WorkRect.Max.x... Maybe provide a default distribution for _WidthAuto columns?
    719 void ImGui::TableUpdateLayout(ImGuiTable* table)
    720 {
    721     ImGuiContext& g = *GImGui;
    722     IM_ASSERT(table->IsLayoutLocked == false);
    723 
    724     const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_);
    725     table->IsDefaultDisplayOrder = true;
    726     table->ColumnsEnabledCount = 0;
    727     table->EnabledMaskByIndex = 0x00;
    728     table->EnabledMaskByDisplayOrder = 0x00;
    729     table->LeftMostEnabledColumn = -1;
    730     table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE
    731 
    732     // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns.
    733     // Process columns in their visible orders as we are building the Prev/Next indices.
    734     int count_fixed = 0;                // Number of columns that have fixed sizing policies
    735     int count_stretch = 0;              // Number of columns that have stretch sizing policies
    736     int prev_visible_column_idx = -1;
    737     bool has_auto_fit_request = false;
    738     bool has_resizable = false;
    739     float stretch_sum_width_auto = 0.0f;
    740     float fixed_max_width_auto = 0.0f;
    741     for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
    742     {
    743         const int column_n = table->DisplayOrderToIndex[order_n];
    744         if (column_n != order_n)
    745             table->IsDefaultDisplayOrder = false;
    746         ImGuiTableColumn* column = &table->Columns[column_n];
    747 
    748         // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame.
    749         // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway.
    750         // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect.
    751         if (table->DeclColumnsCount <= column_n)
    752         {
    753             TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None);
    754             column->NameOffset = -1;
    755             column->UserID = 0;
    756             column->InitStretchWeightOrWidth = -1.0f;
    757         }
    758 
    759         // Update Enabled state, mark settings/sortspecs dirty
    760         if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide))
    761             column->IsEnabledNextFrame = true;
    762         if (column->IsEnabled != column->IsEnabledNextFrame)
    763         {
    764             column->IsEnabled = column->IsEnabledNextFrame;
    765             table->IsSettingsDirty = true;
    766             if (!column->IsEnabled && column->SortOrder != -1)
    767                 table->IsSortSpecsDirty = true;
    768         }
    769         if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti))
    770             table->IsSortSpecsDirty = true;
    771 
    772         // Auto-fit unsized columns
    773         const bool start_auto_fit = (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f);
    774         if (start_auto_fit)
    775             column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames
    776 
    777         if (!column->IsEnabled)
    778         {
    779             column->IndexWithinEnabledSet = -1;
    780             continue;
    781         }
    782 
    783         // Mark as enabled and link to previous/next enabled column
    784         column->PrevEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx;
    785         column->NextEnabledColumn = -1;
    786         if (prev_visible_column_idx != -1)
    787             table->Columns[prev_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n;
    788         else
    789             table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n;
    790         column->IndexWithinEnabledSet = table->ColumnsEnabledCount++;
    791         table->EnabledMaskByIndex |= (ImU64)1 << column_n;
    792         table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder;
    793         prev_visible_column_idx = column_n;
    794         IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder);
    795 
    796         // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping)
    797         // Combine width from regular rows + width from headers unless requested not to.
    798         if (!column->IsPreserveWidthAuto)
    799             column->WidthAuto = TableGetColumnWidthAuto(table, column);
    800 
    801         // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto)
    802         const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0;
    803         if (column_is_resizable)
    804             has_resizable = true;
    805         if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f && !column_is_resizable)
    806             column->WidthAuto = column->InitStretchWeightOrWidth;
    807 
    808         if (column->AutoFitQueue != 0x00)
    809             has_auto_fit_request = true;
    810         if (column->Flags & ImGuiTableColumnFlags_WidthStretch)
    811         {
    812             stretch_sum_width_auto += column->WidthAuto;
    813             count_stretch++;
    814         }
    815         else
    816         {
    817             fixed_max_width_auto = ImMax(fixed_max_width_auto, column->WidthAuto);
    818             count_fixed++;
    819         }
    820     }
    821     if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate))
    822         table->IsSortSpecsDirty = true;
    823     table->RightMostEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx;
    824     IM_ASSERT(table->LeftMostEnabledColumn >= 0 && table->RightMostEnabledColumn >= 0);
    825 
    826     // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible
    827     // to avoid the column fitting having to wait until the first visible frame of the child container (may or not be a good thing).
    828     // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time.
    829     if (has_auto_fit_request && table->OuterWindow != table->InnerWindow)
    830         table->InnerWindow->SkipItems = false;
    831     if (has_auto_fit_request)
    832         table->IsSettingsDirty = true;
    833 
    834     // [Part 3] Fix column flags and record a few extra information.
    835     float sum_width_requests = 0.0f;        // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns but including spacing/padding.
    836     float stretch_sum_weights = 0.0f;       // Sum of all weights for stretch columns.
    837     table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1;
    838     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
    839     {
    840         if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
    841             continue;
    842         ImGuiTableColumn* column = &table->Columns[column_n];
    843 
    844         const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0;
    845         if (column->Flags & ImGuiTableColumnFlags_WidthFixed)
    846         {
    847             // Apply same widths policy
    848             float width_auto = column->WidthAuto;
    849             if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable))
    850                 width_auto = fixed_max_width_auto;
    851 
    852             // Apply automatic width
    853             // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!)
    854             if (column->AutoFitQueue != 0x00)
    855                 column->WidthRequest = width_auto;
    856             else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)))
    857                 column->WidthRequest = width_auto;
    858 
    859             // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets
    860             // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very
    861             // large height (= first frame scrollbar display very off + clipper would skip lots of items).
    862             // This is merely making the side-effect less extreme, but doesn't properly fixes it.
    863             // FIXME: Move this to ->WidthGiven to avoid temporary lossyless?
    864             // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller.
    865             if (column->AutoFitQueue > 0x01 && table->IsInitializing && !column->IsPreserveWidthAuto)
    866                 column->WidthRequest = ImMax(column->WidthRequest, table->MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale?
    867             sum_width_requests += column->WidthRequest;
    868         }
    869         else
    870         {
    871             // Initialize stretch weight
    872             if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f || !column_is_resizable)
    873             {
    874                 if (column->InitStretchWeightOrWidth > 0.0f)
    875                     column->StretchWeight = column->InitStretchWeightOrWidth;
    876                 else if (table_sizing_policy == ImGuiTableFlags_SizingStretchProp)
    877                     column->StretchWeight = (column->WidthAuto / stretch_sum_width_auto) * count_stretch;
    878                 else
    879                     column->StretchWeight = 1.0f;
    880             }
    881 
    882             stretch_sum_weights += column->StretchWeight;
    883             if (table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder > column->DisplayOrder)
    884                 table->LeftMostStretchedColumn = (ImGuiTableColumnIdx)column_n;
    885             if (table->RightMostStretchedColumn == -1 || table->Columns[table->RightMostStretchedColumn].DisplayOrder < column->DisplayOrder)
    886                 table->RightMostStretchedColumn = (ImGuiTableColumnIdx)column_n;
    887         }
    888         column->IsPreserveWidthAuto = false;
    889         sum_width_requests += table->CellPaddingX * 2.0f;
    890     }
    891     table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed;
    892 
    893     // [Part 4] Apply final widths based on requested widths
    894     const ImRect work_rect = table->WorkRect;
    895     const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
    896     const float width_avail = ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth();
    897     const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests;
    898     float width_remaining_for_stretched_columns = width_avail_for_stretched_columns;
    899     table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount;
    900     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
    901     {
    902         if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
    903             continue;
    904         ImGuiTableColumn* column = &table->Columns[column_n];
    905 
    906         // Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest)
    907         if (column->Flags & ImGuiTableColumnFlags_WidthStretch)
    908         {
    909             float weight_ratio = column->StretchWeight / stretch_sum_weights;
    910             column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, table->MinColumnWidth) + 0.01f);
    911             width_remaining_for_stretched_columns -= column->WidthRequest;
    912         }
    913 
    914         // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column
    915         // See additional comments in TableSetColumnWidth().
    916         if (column->NextEnabledColumn == -1 && table->LeftMostStretchedColumn != -1)
    917             column->Flags |= ImGuiTableColumnFlags_NoDirectResize_;
    918 
    919         // Assign final width, record width in case we will need to shrink
    920         column->WidthGiven = ImFloor(ImMax(column->WidthRequest, table->MinColumnWidth));
    921         table->ColumnsGivenWidth += column->WidthGiven;
    922     }
    923 
    924     // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column).
    925     // Using right-to-left distribution (more likely to match resizing cursor).
    926     if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths))
    927         for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--)
    928         {
    929             if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
    930                 continue;
    931             ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]];
    932             if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch))
    933                 continue;
    934             column->WidthRequest += 1.0f;
    935             column->WidthGiven += 1.0f;
    936             width_remaining_for_stretched_columns -= 1.0f;
    937         }
    938 
    939     table->HoveredColumnBody = -1;
    940     table->HoveredColumnBorder = -1;
    941     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));
    942     const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0);
    943 
    944     // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column
    945     // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping.
    946     int visible_n = 0;
    947     bool offset_x_frozen = (table->FreezeColumnsCount > 0);
    948     float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1;
    949     ImRect host_clip_rect = table->InnerClipRect;
    950     //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2;
    951     table->VisibleMaskByIndex = 0x00;
    952     table->RequestOutputMaskByIndex = 0x00;
    953     for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
    954     {
    955         const int column_n = table->DisplayOrderToIndex[order_n];
    956         ImGuiTableColumn* column = &table->Columns[column_n];
    957 
    958         column->NavLayerCurrent = (ImS8)((table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
    959 
    960         if (offset_x_frozen && table->FreezeColumnsCount == visible_n)
    961         {
    962             offset_x += work_rect.Min.x - table->OuterRect.Min.x;
    963             offset_x_frozen = false;
    964         }
    965 
    966         // Clear status flags
    967         column->Flags &= ~ImGuiTableColumnFlags_StatusMask_;
    968 
    969         if ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0)
    970         {
    971             // Hidden column: clear a few fields and we are done with it for the remainder of the function.
    972             // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper.
    973             column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x;
    974             column->WidthGiven = 0.0f;
    975             column->ClipRect.Min.y = work_rect.Min.y;
    976             column->ClipRect.Max.y = FLT_MAX;
    977             column->ClipRect.ClipWithFull(host_clip_rect);
    978             column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false;
    979             column->IsSkipItems = true;
    980             column->ItemWidth = 1.0f;
    981             continue;
    982         }
    983 
    984         // Detect hovered column
    985         if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x)
    986             table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n;
    987 
    988         // Lock start position
    989         column->MinX = offset_x;
    990 
    991         // Lock width based on start position and minimum/maximum width for this position
    992         float max_width = TableGetMaxColumnWidth(table, column_n);
    993         column->WidthGiven = ImMin(column->WidthGiven, max_width);
    994         column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, table->MinColumnWidth));
    995         column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f;
    996 
    997         // Lock other positions
    998         // - 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.
    999         // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) makes things more consistent when resizing down, tho slightly detrimental to visibility in very-small column.
   1000         // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow.
   1001         // - 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.
   1002         column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1;
   1003         column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max
   1004         column->ItemWidth = ImFloor(column->WidthGiven * 0.65f);
   1005         column->ClipRect.Min.x = column->MinX;
   1006         column->ClipRect.Min.y = work_rect.Min.y;
   1007         column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX;
   1008         column->ClipRect.Max.y = FLT_MAX;
   1009         column->ClipRect.ClipWithFull(host_clip_rect);
   1010 
   1011         // Mark column as Clipped (not in sight)
   1012         // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables.
   1013         // 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.
   1014         // Taking advantage of LastOuterHeight would yield good results there...
   1015         // FIXME-TABLE: Y clipping is disabled because it effectively means not submitting will reduce contents width which is fed to outer_window->DC.CursorMaxPos.x,
   1016         // and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, but could be something else).
   1017         // 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.
   1018         column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x);
   1019         column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y);
   1020         const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY;
   1021         if (is_visible)
   1022             table->VisibleMaskByIndex |= ((ImU64)1 << column_n);
   1023 
   1024         // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output.
   1025         column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0;
   1026         if (column->IsRequestOutput)
   1027             table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n);
   1028 
   1029         // Mark column as SkipItems (ignoring all items/layout)
   1030         column->IsSkipItems = !column->IsEnabled || table->HostSkipItems;
   1031         if (column->IsSkipItems)
   1032             IM_ASSERT(!is_visible);
   1033 
   1034         // Update status flags
   1035         column->Flags |= ImGuiTableColumnFlags_IsEnabled;
   1036         if (is_visible)
   1037             column->Flags |= ImGuiTableColumnFlags_IsVisible;
   1038         if (column->SortOrder != -1)
   1039             column->Flags |= ImGuiTableColumnFlags_IsSorted;
   1040         if (table->HoveredColumnBody == column_n)
   1041             column->Flags |= ImGuiTableColumnFlags_IsHovered;
   1042 
   1043         // Alignment
   1044         // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in
   1045         // many cases (to be able to honor this we might be able to store a log of cells width, per row, for
   1046         // visible rows, but nav/programmatic scroll would have visible artifacts.)
   1047         //if (column->Flags & ImGuiTableColumnFlags_AlignRight)
   1048         //    column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen);
   1049         //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter)
   1050         //    column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f);
   1051 
   1052         // Reset content width variables
   1053         column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX;
   1054         column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX;
   1055 
   1056         // Don't decrement auto-fit counters until container window got a chance to submit its items
   1057         if (table->HostSkipItems == false)
   1058         {
   1059             column->AutoFitQueue >>= 1;
   1060             column->CannotSkipItemsQueue >>= 1;
   1061         }
   1062 
   1063         if (visible_n < table->FreezeColumnsCount)
   1064             host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x);
   1065 
   1066         offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f;
   1067         visible_n++;
   1068     }
   1069 
   1070     // [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it)
   1071     // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either
   1072     // because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu.
   1073     const float unused_x1 = ImMax(table->WorkRect.Min.x, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x);
   1074     if (is_hovering_table && table->HoveredColumnBody == -1)
   1075     {
   1076         if (g.IO.MousePos.x >= unused_x1)
   1077             table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount;
   1078     }
   1079     if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable))
   1080         table->Flags &= ~ImGuiTableFlags_Resizable;
   1081 
   1082     // [Part 8] Lock actual OuterRect/WorkRect right-most position.
   1083     // This is done late to handle the case of fixed-columns tables not claiming more widths that they need.
   1084     // Because of this we are careful with uses of WorkRect and InnerClipRect before this point.
   1085     if (table->RightMostStretchedColumn != -1)
   1086         table->Flags &= ~ImGuiTableFlags_NoHostExtendX;
   1087     if (table->Flags & ImGuiTableFlags_NoHostExtendX)
   1088     {
   1089         table->OuterRect.Max.x = table->WorkRect.Max.x = unused_x1;
   1090         table->InnerClipRect.Max.x = ImMin(table->InnerClipRect.Max.x, unused_x1);
   1091     }
   1092     table->InnerWindow->ParentWorkRect = table->WorkRect;
   1093     table->BorderX1 = table->InnerClipRect.Min.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f);
   1094     table->BorderX2 = table->InnerClipRect.Max.x;// +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f);
   1095 
   1096     // [Part 9] Allocate draw channels and setup background cliprect
   1097     TableSetupDrawChannels(table);
   1098 
   1099     // [Part 10] Hit testing on borders
   1100     if (table->Flags & ImGuiTableFlags_Resizable)
   1101         TableUpdateBorders(table);
   1102     table->LastFirstRowHeight = 0.0f;
   1103     table->IsLayoutLocked = true;
   1104     table->IsUsingHeaders = false;
   1105 
   1106     // [Part 11] Context menu
   1107     if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted)
   1108     {
   1109         const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID);
   1110         if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings))
   1111         {
   1112             TableDrawContextMenu(table);
   1113             EndPopup();
   1114         }
   1115         else
   1116         {
   1117             table->IsContextPopupOpen = false;
   1118         }
   1119     }
   1120 
   1121     // [Part 13] Sanitize and build sort specs before we have a change to use them for display.
   1122     // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change)
   1123     if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable))
   1124         TableSortSpecsBuild(table);
   1125 
   1126     // Initial state
   1127     ImGuiWindow* inner_window = table->InnerWindow;
   1128     if (table->Flags & ImGuiTableFlags_NoClip)
   1129         table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);
   1130     else
   1131         inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false);
   1132 }
   1133 
   1134 // Process hit-testing on resizing borders. Actual size change will be applied in EndTable()
   1135 // - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise
   1136 // - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets
   1137 //   overlapping the same area.
   1138 void ImGui::TableUpdateBorders(ImGuiTable* table)
   1139 {
   1140     ImGuiContext& g = *GImGui;
   1141     IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable);
   1142 
   1143     // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and
   1144     // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not
   1145     // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height).
   1146     // Actual columns highlight/render will be performed in EndTable() and not be affected.
   1147     const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS;
   1148     const float hit_y1 = table->OuterRect.Min.y;
   1149     const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table->LastOuterHeight);
   1150     const float hit_y2_head = hit_y1 + table->LastFirstRowHeight;
   1151 
   1152     for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
   1153     {
   1154         if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
   1155             continue;
   1156 
   1157         const int column_n = table->DisplayOrderToIndex[order_n];
   1158         ImGuiTableColumn* column = &table->Columns[column_n];
   1159         if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_))
   1160             continue;
   1161 
   1162         // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders()
   1163         const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body;
   1164         if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false)
   1165             continue;
   1166 
   1167         if (table->FreezeColumnsCount > 0)
   1168             if (column->MaxX < table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsCount - 1]].MaxX)
   1169                 continue;
   1170 
   1171         ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent);
   1172         ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit);
   1173         //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100));
   1174         KeepAliveID(column_id);
   1175 
   1176         bool hovered = false, held = false;
   1177         bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);
   1178         if (pressed && IsMouseDoubleClicked(0))
   1179         {
   1180             TableSetColumnWidthAutoSingle(table, column_n);
   1181             ClearActiveID();
   1182             held = hovered = false;
   1183         }
   1184         if (held)
   1185         {
   1186             if (table->LastResizedColumn == -1)
   1187                 table->ResizeLockMinContentsX2 = table->RightMostEnabledColumn != -1 ? table->Columns[table->RightMostEnabledColumn].MaxX : -FLT_MAX;
   1188             table->ResizedColumn = (ImGuiTableColumnIdx)column_n;
   1189             table->InstanceInteracted = table->InstanceCurrent;
   1190         }
   1191         if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held)
   1192         {
   1193             table->HoveredColumnBorder = (ImGuiTableColumnIdx)column_n;
   1194             SetMouseCursor(ImGuiMouseCursor_ResizeEW);
   1195         }
   1196     }
   1197 }
   1198 
   1199 void    ImGui::EndTable()
   1200 {
   1201     ImGuiContext& g = *GImGui;
   1202     ImGuiTable* table = g.CurrentTable;
   1203     IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!");
   1204 
   1205     // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some
   1206     // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border)
   1207     //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?");
   1208 
   1209     // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our
   1210     // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc.
   1211     if (!table->IsLayoutLocked)
   1212         TableUpdateLayout(table);
   1213 
   1214     const ImGuiTableFlags flags = table->Flags;
   1215     ImGuiWindow* inner_window = table->InnerWindow;
   1216     ImGuiWindow* outer_window = table->OuterWindow;
   1217     ImGuiTableTempData* temp_data = table->TempData;
   1218     IM_ASSERT(inner_window == g.CurrentWindow);
   1219     IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow);
   1220 
   1221     if (table->IsInsideRow)
   1222         TableEndRow(table);
   1223 
   1224     // Context menu in columns body
   1225     if (flags & ImGuiTableFlags_ContextMenuInBody)
   1226         if (table->HoveredColumnBody != -1 && !IsAnyItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))
   1227             TableOpenContextMenu((int)table->HoveredColumnBody);
   1228 
   1229     // Finalize table height
   1230     inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize;
   1231     inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize;
   1232     inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos;
   1233     const float inner_content_max_y = table->RowPosY2;
   1234     IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y);
   1235     if (inner_window != outer_window)
   1236         inner_window->DC.CursorMaxPos.y = inner_content_max_y;
   1237     else if (!(flags & ImGuiTableFlags_NoHostExtendY))
   1238         table->OuterRect.Max.y = table->InnerRect.Max.y = ImMax(table->OuterRect.Max.y, inner_content_max_y); // Patch OuterRect/InnerRect height
   1239     table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y);
   1240     table->LastOuterHeight = table->OuterRect.GetHeight();
   1241 
   1242     // Setup inner scrolling range
   1243     // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y,
   1244     // but since the later is likely to be impossible to do we'd rather update both axises together.
   1245     if (table->Flags & ImGuiTableFlags_ScrollX)
   1246     {
   1247         const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
   1248         float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x;
   1249         if (table->RightMostEnabledColumn != -1)
   1250             max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border);
   1251         if (table->ResizedColumn != -1)
   1252             max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2);
   1253         table->InnerWindow->DC.CursorMaxPos.x = max_pos_x;
   1254     }
   1255 
   1256     // Pop clipping rect
   1257     if (!(flags & ImGuiTableFlags_NoClip))
   1258         inner_window->DrawList->PopClipRect();
   1259     inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back();
   1260 
   1261     // Draw borders
   1262     if ((flags & ImGuiTableFlags_Borders) != 0)
   1263         TableDrawBorders(table);
   1264 
   1265 #if 0
   1266     // Strip out dummy channel draw calls
   1267     // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out)
   1268     // Pros: remove draw calls which will have no effect. since they'll have zero-size cliprect they may be early out anyway.
   1269     // Cons: making it harder for users watching metrics/debugger to spot the wasted vertices.
   1270     if (table->DummyDrawChannel != (ImGuiTableColumnIdx)-1)
   1271     {
   1272         ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel];
   1273         dummy_channel->_CmdBuffer.resize(0);
   1274         dummy_channel->_IdxBuffer.resize(0);
   1275     }
   1276 #endif
   1277 
   1278     // Flatten channels and merge draw calls
   1279     ImDrawListSplitter* splitter = table->DrawSplitter;
   1280     splitter->SetCurrentChannel(inner_window->DrawList, 0);
   1281     if ((table->Flags & ImGuiTableFlags_NoClip) == 0)
   1282         TableMergeDrawChannels(table);
   1283     splitter->Merge(inner_window->DrawList);
   1284 
   1285     // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable()
   1286     const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
   1287     table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount;
   1288     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   1289         if (table->EnabledMaskByIndex & ((ImU64)1 << column_n))
   1290         {
   1291             ImGuiTableColumn* column = &table->Columns[column_n];
   1292             if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize))
   1293                 table->ColumnsAutoFitWidth += column->WidthRequest;
   1294             else
   1295                 table->ColumnsAutoFitWidth += TableGetColumnWidthAuto(table, column);
   1296         }
   1297 
   1298     // Update scroll
   1299     if ((table->Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window)
   1300     {
   1301         inner_window->Scroll.x = 0.0f;
   1302     }
   1303     else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceCurrent)
   1304     {
   1305         // When releasing a column being resized, scroll to keep the resulting column in sight
   1306         const float neighbor_width_to_keep_visible = table->MinColumnWidth + table->CellPaddingX * 2.0f;
   1307         ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn];
   1308         if (column->MaxX < table->InnerClipRect.Min.x)
   1309             SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f);
   1310         else if (column->MaxX > table->InnerClipRect.Max.x)
   1311             SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f);
   1312     }
   1313 
   1314     // Apply resizing/dragging at the end of the frame
   1315     if (table->ResizedColumn != -1 && table->InstanceCurrent == table->InstanceInteracted)
   1316     {
   1317         ImGuiTableColumn* column = &table->Columns[table->ResizedColumn];
   1318         const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS);
   1319         const float new_width = ImFloor(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f);
   1320         table->ResizedColumnNextWidth = new_width;
   1321     }
   1322 
   1323     // Pop from id stack
   1324     IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!");
   1325     IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!");
   1326     PopID();
   1327 
   1328     // Restore window data that we modified
   1329     const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos;
   1330     inner_window->WorkRect = temp_data->HostBackupWorkRect;
   1331     inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect;
   1332     inner_window->SkipItems = table->HostSkipItems;
   1333     outer_window->DC.CursorPos = table->OuterRect.Min;
   1334     outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth;
   1335     outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize;
   1336     outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset;
   1337 
   1338     // Layout in outer window
   1339     // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding
   1340     // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414)
   1341     if (inner_window != outer_window)
   1342     {
   1343         EndChild();
   1344     }
   1345     else
   1346     {
   1347         ItemSize(table->OuterRect.GetSize());
   1348         ItemAdd(table->OuterRect, 0);
   1349     }
   1350 
   1351     // Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar
   1352     if (table->Flags & ImGuiTableFlags_NoHostExtendX)
   1353     {
   1354         // FIXME-TABLE: Could we remove this section?
   1355         // ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents
   1356         IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0);
   1357         outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth);
   1358     }
   1359     else if (temp_data->UserOuterSize.x <= 0.0f)
   1360     {
   1361         const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f;
   1362         outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x);
   1363         outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth));
   1364     }
   1365     else
   1366     {
   1367         outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x);
   1368     }
   1369     if (temp_data->UserOuterSize.y <= 0.0f)
   1370     {
   1371         const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f;
   1372         outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y);
   1373         outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y));
   1374     }
   1375     else
   1376     {
   1377         // OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set)
   1378         outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table->OuterRect.Max.y);
   1379     }
   1380 
   1381     // Save settings
   1382     if (table->IsSettingsDirty)
   1383         TableSaveSettings(table);
   1384     table->IsInitializing = false;
   1385 
   1386     // Clear or restore current table, if any
   1387     IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table);
   1388     IM_ASSERT(g.CurrentTableStackIdx >= 0);
   1389     g.CurrentTableStackIdx--;
   1390     temp_data = g.CurrentTableStackIdx >= 0 ? &g.TablesTempDataStack[g.CurrentTableStackIdx] : NULL;
   1391     g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL;
   1392     if (g.CurrentTable)
   1393     {
   1394         g.CurrentTable->TempData = temp_data;
   1395         g.CurrentTable->DrawSplitter = &temp_data->DrawSplitter;
   1396     }
   1397     outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1;
   1398 }
   1399 
   1400 // See "COLUMN SIZING POLICIES" comments at the top of this file
   1401 // If (init_width_or_weight <= 0.0f) it is ignored
   1402 void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id)
   1403 {
   1404     ImGuiContext& g = *GImGui;
   1405     ImGuiTable* table = g.CurrentTable;
   1406     IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!");
   1407     IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!");
   1408     IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()");
   1409     if (table->DeclColumnsCount >= table->ColumnsCount)
   1410     {
   1411         IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, "Called TableSetupColumn() too many times!");
   1412         return;
   1413     }
   1414 
   1415     ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount];
   1416     table->DeclColumnsCount++;
   1417 
   1418     // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa.
   1419     // Give a grace to users of ImGuiTableFlags_ScrollX.
   1420     if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0)
   1421         IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column.");
   1422 
   1423     // When passing a width automatically enforce WidthFixed policy
   1424     // (whereas TableSetupColumnFlags would default to WidthAuto if table is not Resizable)
   1425     if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f)
   1426         if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)
   1427             flags |= ImGuiTableColumnFlags_WidthFixed;
   1428 
   1429     TableSetupColumnFlags(table, column, flags);
   1430     column->UserID = user_id;
   1431     flags = column->Flags;
   1432 
   1433     // Initialize defaults
   1434     column->InitStretchWeightOrWidth = init_width_or_weight;
   1435     if (table->IsInitializing)
   1436     {
   1437         // Init width or weight
   1438         if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f)
   1439         {
   1440             if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f)
   1441                 column->WidthRequest = init_width_or_weight;
   1442             if (flags & ImGuiTableColumnFlags_WidthStretch)
   1443                 column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f;
   1444 
   1445             // Disable auto-fit if an explicit width/weight has been specified
   1446             if (init_width_or_weight > 0.0f)
   1447                 column->AutoFitQueue = 0x00;
   1448         }
   1449 
   1450         // Init default visibility/sort state
   1451         if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0)
   1452             column->IsEnabled = column->IsEnabledNextFrame = false;
   1453         if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0)
   1454         {
   1455             column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs.
   1456             column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending);
   1457         }
   1458     }
   1459 
   1460     // Store name (append with zero-terminator in contiguous buffer)
   1461     column->NameOffset = -1;
   1462     if (label != NULL && label[0] != 0)
   1463     {
   1464         column->NameOffset = (ImS16)table->ColumnsNames.size();
   1465         table->ColumnsNames.append(label, label + strlen(label) + 1);
   1466     }
   1467 }
   1468 
   1469 // [Public]
   1470 void ImGui::TableSetupScrollFreeze(int columns, int rows)
   1471 {
   1472     ImGuiContext& g = *GImGui;
   1473     ImGuiTable* table = g.CurrentTable;
   1474     IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!");
   1475     IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!");
   1476     IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS);
   1477     IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit
   1478 
   1479     table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)columns : 0;
   1480     table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0;
   1481     table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0;
   1482     table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0;
   1483     table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b
   1484 }
   1485 
   1486 //-----------------------------------------------------------------------------
   1487 // [SECTION] Tables: Simple accessors
   1488 //-----------------------------------------------------------------------------
   1489 // - TableGetColumnCount()
   1490 // - TableGetColumnName()
   1491 // - TableGetColumnName() [Internal]
   1492 // - TableSetColumnEnabled() [Internal]
   1493 // - TableGetColumnFlags()
   1494 // - TableGetCellBgRect() [Internal]
   1495 // - TableGetColumnResizeID() [Internal]
   1496 // - TableGetHoveredColumn() [Internal]
   1497 // - TableSetBgColor()
   1498 //-----------------------------------------------------------------------------
   1499 
   1500 int ImGui::TableGetColumnCount()
   1501 {
   1502     ImGuiContext& g = *GImGui;
   1503     ImGuiTable* table = g.CurrentTable;
   1504     return table ? table->ColumnsCount : 0;
   1505 }
   1506 
   1507 const char* ImGui::TableGetColumnName(int column_n)
   1508 {
   1509     ImGuiContext& g = *GImGui;
   1510     ImGuiTable* table = g.CurrentTable;
   1511     if (!table)
   1512         return NULL;
   1513     if (column_n < 0)
   1514         column_n = table->CurrentColumn;
   1515     return TableGetColumnName(table, column_n);
   1516 }
   1517 
   1518 const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n)
   1519 {
   1520     if (table->IsLayoutLocked == false && column_n >= table->DeclColumnsCount)
   1521         return ""; // NameOffset is invalid at this point
   1522     const ImGuiTableColumn* column = &table->Columns[column_n];
   1523     if (column->NameOffset == -1)
   1524         return "";
   1525     return &table->ColumnsNames.Buf[column->NameOffset];
   1526 }
   1527 
   1528 // Request enabling/disabling a column (often perceived as "showing/hiding" from users point of view)
   1529 // Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody)
   1530 // Request will be applied during next layout, which happens on the first call to TableNextRow() after BeginTable()
   1531 // For the getter you can use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled)
   1532 void ImGui::TableSetColumnEnabled(int column_n, bool enabled)
   1533 {
   1534     ImGuiContext& g = *GImGui;
   1535     ImGuiTable* table = g.CurrentTable;
   1536     IM_ASSERT(table != NULL);
   1537     if (!table)
   1538         return;
   1539     if (column_n < 0)
   1540         column_n = table->CurrentColumn;
   1541     IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
   1542     ImGuiTableColumn* column = &table->Columns[column_n];
   1543     column->IsEnabledNextFrame = enabled;
   1544 }
   1545 
   1546 // We allow querying for an extra column in order to poll the IsHovered state of the right-most section
   1547 ImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n)
   1548 {
   1549     ImGuiContext& g = *GImGui;
   1550     ImGuiTable* table = g.CurrentTable;
   1551     if (!table)
   1552         return ImGuiTableColumnFlags_None;
   1553     if (column_n < 0)
   1554         column_n = table->CurrentColumn;
   1555     if (column_n == table->ColumnsCount)
   1556         return (table->HoveredColumnBody == column_n) ? ImGuiTableColumnFlags_IsHovered : ImGuiTableColumnFlags_None;
   1557     return table->Columns[column_n].Flags;
   1558 }
   1559 
   1560 // Return the cell rectangle based on currently known height.
   1561 // - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations.
   1562 //   The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it.
   1563 // - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right
   1564 //   columns report a small offset so their CellBgRect can extend up to the outer border.
   1565 ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n)
   1566 {
   1567     const ImGuiTableColumn* column = &table->Columns[column_n];
   1568     float x1 = column->MinX;
   1569     float x2 = column->MaxX;
   1570     if (column->PrevEnabledColumn == -1)
   1571         x1 -= table->CellSpacingX1;
   1572     if (column->NextEnabledColumn == -1)
   1573         x2 += table->CellSpacingX2;
   1574     return ImRect(x1, table->RowPosY1, x2, table->RowPosY2);
   1575 }
   1576 
   1577 // Return the resizing ID for the right-side of the given column.
   1578 ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no)
   1579 {
   1580     IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
   1581     ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n;
   1582     return id;
   1583 }
   1584 
   1585 // Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered.
   1586 int ImGui::TableGetHoveredColumn()
   1587 {
   1588     ImGuiContext& g = *GImGui;
   1589     ImGuiTable* table = g.CurrentTable;
   1590     if (!table)
   1591         return -1;
   1592     return (int)table->HoveredColumnBody;
   1593 }
   1594 
   1595 void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n)
   1596 {
   1597     ImGuiContext& g = *GImGui;
   1598     ImGuiTable* table = g.CurrentTable;
   1599     IM_ASSERT(target != ImGuiTableBgTarget_None);
   1600 
   1601     if (color == IM_COL32_DISABLE)
   1602         color = 0;
   1603 
   1604     // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time.
   1605     switch (target)
   1606     {
   1607     case ImGuiTableBgTarget_CellBg:
   1608     {
   1609         if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard
   1610             return;
   1611         if (column_n == -1)
   1612             column_n = table->CurrentColumn;
   1613         if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0)
   1614             return;
   1615         if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n)
   1616             table->RowCellDataCurrent++;
   1617         ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCurrent];
   1618         cell_data->BgColor = color;
   1619         cell_data->Column = (ImGuiTableColumnIdx)column_n;
   1620         break;
   1621     }
   1622     case ImGuiTableBgTarget_RowBg0:
   1623     case ImGuiTableBgTarget_RowBg1:
   1624     {
   1625         if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard
   1626             return;
   1627         IM_ASSERT(column_n == -1);
   1628         int bg_idx = (target == ImGuiTableBgTarget_RowBg1) ? 1 : 0;
   1629         table->RowBgColor[bg_idx] = color;
   1630         break;
   1631     }
   1632     default:
   1633         IM_ASSERT(0);
   1634     }
   1635 }
   1636 
   1637 //-------------------------------------------------------------------------
   1638 // [SECTION] Tables: Row changes
   1639 //-------------------------------------------------------------------------
   1640 // - TableGetRowIndex()
   1641 // - TableNextRow()
   1642 // - TableBeginRow() [Internal]
   1643 // - TableEndRow() [Internal]
   1644 //-------------------------------------------------------------------------
   1645 
   1646 // [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows
   1647 int ImGui::TableGetRowIndex()
   1648 {
   1649     ImGuiContext& g = *GImGui;
   1650     ImGuiTable* table = g.CurrentTable;
   1651     if (!table)
   1652         return 0;
   1653     return table->CurrentRow;
   1654 }
   1655 
   1656 // [Public] Starts into the first cell of a new row
   1657 void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height)
   1658 {
   1659     ImGuiContext& g = *GImGui;
   1660     ImGuiTable* table = g.CurrentTable;
   1661 
   1662     if (!table->IsLayoutLocked)
   1663         TableUpdateLayout(table);
   1664     if (table->IsInsideRow)
   1665         TableEndRow(table);
   1666 
   1667     table->LastRowFlags = table->RowFlags;
   1668     table->RowFlags = row_flags;
   1669     table->RowMinHeight = row_min_height;
   1670     TableBeginRow(table);
   1671 
   1672     // We honor min_row_height requested by user, but cannot guarantee per-row maximum height,
   1673     // because that would essentially require a unique clipping rectangle per-cell.
   1674     table->RowPosY2 += table->CellPaddingY * 2.0f;
   1675     table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height);
   1676 
   1677     // Disable output until user calls TableNextColumn()
   1678     table->InnerWindow->SkipItems = true;
   1679 }
   1680 
   1681 // [Internal] Called by TableNextRow()
   1682 void ImGui::TableBeginRow(ImGuiTable* table)
   1683 {
   1684     ImGuiWindow* window = table->InnerWindow;
   1685     IM_ASSERT(!table->IsInsideRow);
   1686 
   1687     // New row
   1688     table->CurrentRow++;
   1689     table->CurrentColumn = -1;
   1690     table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE;
   1691     table->RowCellDataCurrent = -1;
   1692     table->IsInsideRow = true;
   1693 
   1694     // Begin frozen rows
   1695     float next_y1 = table->RowPosY2;
   1696     if (table->CurrentRow == 0 && table->FreezeRowsCount > 0)
   1697         next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y;
   1698 
   1699     table->RowPosY1 = table->RowPosY2 = next_y1;
   1700     table->RowTextBaseline = 0.0f;
   1701     table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent
   1702     window->DC.PrevLineTextBaseOffset = 0.0f;
   1703     window->DC.CursorMaxPos.y = next_y1;
   1704 
   1705     // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging.
   1706     if (table->RowFlags & ImGuiTableRowFlags_Headers)
   1707     {
   1708         TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg));
   1709         if (table->CurrentRow == 0)
   1710             table->IsUsingHeaders = true;
   1711     }
   1712 }
   1713 
   1714 // [Internal] Called by TableNextRow()
   1715 void ImGui::TableEndRow(ImGuiTable* table)
   1716 {
   1717     ImGuiContext& g = *GImGui;
   1718     ImGuiWindow* window = g.CurrentWindow;
   1719     IM_ASSERT(window == table->InnerWindow);
   1720     IM_ASSERT(table->IsInsideRow);
   1721 
   1722     if (table->CurrentColumn != -1)
   1723         TableEndCell(table);
   1724 
   1725     // Logging
   1726     if (g.LogEnabled)
   1727         LogRenderedText(NULL, "|");
   1728 
   1729     // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is
   1730     // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding.
   1731     window->DC.CursorPos.y = table->RowPosY2;
   1732 
   1733     // Row background fill
   1734     const float bg_y1 = table->RowPosY1;
   1735     const float bg_y2 = table->RowPosY2;
   1736     const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount);
   1737     const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest);
   1738     if (table->CurrentRow == 0)
   1739         table->LastFirstRowHeight = bg_y2 - bg_y1;
   1740 
   1741     const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y);
   1742     if (is_visible)
   1743     {
   1744         // Decide of background color for the row
   1745         ImU32 bg_col0 = 0;
   1746         ImU32 bg_col1 = 0;
   1747         if (table->RowBgColor[0] != IM_COL32_DISABLE)
   1748             bg_col0 = table->RowBgColor[0];
   1749         else if (table->Flags & ImGuiTableFlags_RowBg)
   1750             bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg);
   1751         if (table->RowBgColor[1] != IM_COL32_DISABLE)
   1752             bg_col1 = table->RowBgColor[1];
   1753 
   1754         // Decide of top border color
   1755         ImU32 border_col = 0;
   1756         const float border_size = TABLE_BORDER_SIZE;
   1757         if (table->CurrentRow > 0 || table->InnerWindow == table->OuterWindow)
   1758             if (table->Flags & ImGuiTableFlags_BordersInnerH)
   1759                 border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight;
   1760 
   1761         const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0;
   1762         const bool draw_strong_bottom_border = unfreeze_rows_actual;
   1763         if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color)
   1764         {
   1765             // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is
   1766             // always followed by a change of clipping rectangle we perform the smallest overwrite possible here.
   1767             if ((table->Flags & ImGuiTableFlags_NoClip) == 0)
   1768                 window->DrawList->_CmdHeader.ClipRect = table->Bg0ClipRectForDrawCmd.ToVec4();
   1769             table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0);
   1770         }
   1771 
   1772         // Draw row background
   1773         // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle
   1774         if (bg_col0 || bg_col1)
   1775         {
   1776             ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2);
   1777             row_rect.ClipWith(table->BgClipRect);
   1778             if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y)
   1779                 window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0);
   1780             if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y)
   1781                 window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1);
   1782         }
   1783 
   1784         // Draw cell background color
   1785         if (draw_cell_bg_color)
   1786         {
   1787             ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCurrent];
   1788             for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++)
   1789             {
   1790                 const ImGuiTableColumn* column = &table->Columns[cell_data->Column];
   1791                 ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column);
   1792                 cell_bg_rect.ClipWith(table->BgClipRect);
   1793                 cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x);     // So that first column after frozen one gets clipped
   1794                 cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX);
   1795                 window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor);
   1796             }
   1797         }
   1798 
   1799         // Draw top border
   1800         if (border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y)
   1801             window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), border_col, border_size);
   1802 
   1803         // Draw bottom border at the row unfreezing mark (always strong)
   1804         if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y)
   1805             window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size);
   1806     }
   1807 
   1808     // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle)
   1809     // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and
   1810     // get the new cursor position.
   1811     if (unfreeze_rows_request)
   1812         for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   1813         {
   1814             ImGuiTableColumn* column = &table->Columns[column_n];
   1815             column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
   1816         }
   1817     if (unfreeze_rows_actual)
   1818     {
   1819         IM_ASSERT(table->IsUnfrozenRows == false);
   1820         table->IsUnfrozenRows = true;
   1821 
   1822         // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect
   1823         float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y);
   1824         table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y);
   1825         table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y;
   1826         table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen;
   1827         IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y);
   1828 
   1829         float row_height = table->RowPosY2 - table->RowPosY1;
   1830         table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y;
   1831         table->RowPosY1 = table->RowPosY2 - row_height;
   1832         for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   1833         {
   1834             ImGuiTableColumn* column = &table->Columns[column_n];
   1835             column->DrawChannelCurrent = column->DrawChannelUnfrozen;
   1836             column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y;
   1837         }
   1838 
   1839         // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y
   1840         SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect);
   1841         table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent);
   1842     }
   1843 
   1844     if (!(table->RowFlags & ImGuiTableRowFlags_Headers))
   1845         table->RowBgColorCounter++;
   1846     table->IsInsideRow = false;
   1847 }
   1848 
   1849 //-------------------------------------------------------------------------
   1850 // [SECTION] Tables: Columns changes
   1851 //-------------------------------------------------------------------------
   1852 // - TableGetColumnIndex()
   1853 // - TableSetColumnIndex()
   1854 // - TableNextColumn()
   1855 // - TableBeginCell() [Internal]
   1856 // - TableEndCell() [Internal]
   1857 //-------------------------------------------------------------------------
   1858 
   1859 int ImGui::TableGetColumnIndex()
   1860 {
   1861     ImGuiContext& g = *GImGui;
   1862     ImGuiTable* table = g.CurrentTable;
   1863     if (!table)
   1864         return 0;
   1865     return table->CurrentColumn;
   1866 }
   1867 
   1868 // [Public] Append into a specific column
   1869 bool ImGui::TableSetColumnIndex(int column_n)
   1870 {
   1871     ImGuiContext& g = *GImGui;
   1872     ImGuiTable* table = g.CurrentTable;
   1873     if (!table)
   1874         return false;
   1875 
   1876     if (table->CurrentColumn != column_n)
   1877     {
   1878         if (table->CurrentColumn != -1)
   1879             TableEndCell(table);
   1880         IM_ASSERT(column_n >= 0 && table->ColumnsCount);
   1881         TableBeginCell(table, column_n);
   1882     }
   1883 
   1884     // Return whether the column is visible. User may choose to skip submitting items based on this return value,
   1885     // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
   1886     return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0;
   1887 }
   1888 
   1889 // [Public] Append into the next column, wrap and create a new row when already on last column
   1890 bool ImGui::TableNextColumn()
   1891 {
   1892     ImGuiContext& g = *GImGui;
   1893     ImGuiTable* table = g.CurrentTable;
   1894     if (!table)
   1895         return false;
   1896 
   1897     if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount)
   1898     {
   1899         if (table->CurrentColumn != -1)
   1900             TableEndCell(table);
   1901         TableBeginCell(table, table->CurrentColumn + 1);
   1902     }
   1903     else
   1904     {
   1905         TableNextRow();
   1906         TableBeginCell(table, 0);
   1907     }
   1908 
   1909     // Return whether the column is visible. User may choose to skip submitting items based on this return value,
   1910     // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
   1911     int column_n = table->CurrentColumn;
   1912     return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0;
   1913 }
   1914 
   1915 
   1916 // [Internal] Called by TableSetColumnIndex()/TableNextColumn()
   1917 // This is called very frequently, so we need to be mindful of unnecessary overhead.
   1918 // FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns.
   1919 void ImGui::TableBeginCell(ImGuiTable* table, int column_n)
   1920 {
   1921     ImGuiTableColumn* column = &table->Columns[column_n];
   1922     ImGuiWindow* window = table->InnerWindow;
   1923     table->CurrentColumn = column_n;
   1924 
   1925     // Start position is roughly ~~ CellRect.Min + CellPadding + Indent
   1926     float start_x = column->WorkMinX;
   1927     if (column->Flags & ImGuiTableColumnFlags_IndentEnable)
   1928         start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row.
   1929 
   1930     window->DC.CursorPos.x = start_x;
   1931     window->DC.CursorPos.y = table->RowPosY1 + table->CellPaddingY;
   1932     window->DC.CursorMaxPos.x = window->DC.CursorPos.x;
   1933     window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT
   1934     window->DC.CurrLineTextBaseOffset = table->RowTextBaseline;
   1935     window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent;
   1936 
   1937     window->WorkRect.Min.y = window->DC.CursorPos.y;
   1938     window->WorkRect.Min.x = column->WorkMinX;
   1939     window->WorkRect.Max.x = column->WorkMaxX;
   1940     window->DC.ItemWidth = column->ItemWidth;
   1941 
   1942     // To allow ImGuiListClipper to function we propagate our row height
   1943     if (!column->IsEnabled)
   1944         window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2);
   1945 
   1946     window->SkipItems = column->IsSkipItems;
   1947     if (column->IsSkipItems)
   1948     {
   1949         window->DC.LastItemId = 0;
   1950         window->DC.LastItemStatusFlags = 0;
   1951     }
   1952 
   1953     if (table->Flags & ImGuiTableFlags_NoClip)
   1954     {
   1955         // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.
   1956         table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);
   1957         //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP);
   1958     }
   1959     else
   1960     {
   1961         // FIXME-TABLE: Could avoid this if draw channel is dummy channel?
   1962         SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
   1963         table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
   1964     }
   1965 
   1966     // Logging
   1967     ImGuiContext& g = *GImGui;
   1968     if (g.LogEnabled && !column->IsSkipItems)
   1969     {
   1970         LogRenderedText(&window->DC.CursorPos, "|");
   1971         g.LogLinePosY = FLT_MAX;
   1972     }
   1973 }
   1974 
   1975 // [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn()
   1976 void ImGui::TableEndCell(ImGuiTable* table)
   1977 {
   1978     ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
   1979     ImGuiWindow* window = table->InnerWindow;
   1980 
   1981     // Report maximum position so we can infer content size per column.
   1982     float* p_max_pos_x;
   1983     if (table->RowFlags & ImGuiTableRowFlags_Headers)
   1984         p_max_pos_x = &column->ContentMaxXHeadersUsed;  // Useful in case user submit contents in header row that is not a TableHeader() call
   1985     else
   1986         p_max_pos_x = table->IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen;
   1987     *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x);
   1988     table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY);
   1989     column->ItemWidth = window->DC.ItemWidth;
   1990 
   1991     // Propagate text baseline for the entire row
   1992     // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one.
   1993     table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset);
   1994 }
   1995 
   1996 //-------------------------------------------------------------------------
   1997 // [SECTION] Tables: Columns width management
   1998 //-------------------------------------------------------------------------
   1999 // - TableGetMaxColumnWidth() [Internal]
   2000 // - TableGetColumnWidthAuto() [Internal]
   2001 // - TableSetColumnWidth()
   2002 // - TableSetColumnWidthAutoSingle() [Internal]
   2003 // - TableSetColumnWidthAutoAll() [Internal]
   2004 // - TableUpdateColumnsWeightFromWidth() [Internal]
   2005 //-------------------------------------------------------------------------
   2006 
   2007 // Maximum column content width given current layout. Use column->MinX so this value on a per-column basis.
   2008 float ImGui::TableGetMaxColumnWidth(const ImGuiTable* table, int column_n)
   2009 {
   2010     const ImGuiTableColumn* column = &table->Columns[column_n];
   2011     float max_width = FLT_MAX;
   2012     const float min_column_distance = table->MinColumnWidth + table->CellPaddingX * 2.0f + table->CellSpacingX1 + table->CellSpacingX2;
   2013     if (table->Flags & ImGuiTableFlags_ScrollX)
   2014     {
   2015         // Frozen columns can't reach beyond visible width else scrolling will naturally break.
   2016         if (column->DisplayOrder < table->FreezeColumnsRequest)
   2017         {
   2018             max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX;
   2019             max_width = max_width - table->OuterPaddingX - table->CellPaddingX - table->CellSpacingX2;
   2020         }
   2021     }
   2022     else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0)
   2023     {
   2024         // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make
   2025         // sure they are all visible. Because of this we also know that all of the columns will always fit in
   2026         // table->WorkRect and therefore in table->InnerRect (because ScrollX is off)
   2027         // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match.
   2028         // See "table_width_distrib" and "table_width_keep_visible" tests
   2029         max_width = table->WorkRect.Max.x - (table->ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - column->MinX;
   2030         //max_width -= table->CellSpacingX1;
   2031         max_width -= table->CellSpacingX2;
   2032         max_width -= table->CellPaddingX * 2.0f;
   2033         max_width -= table->OuterPaddingX;
   2034     }
   2035     return max_width;
   2036 }
   2037 
   2038 // Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field
   2039 float ImGui::TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column)
   2040 {
   2041     const float content_width_body = ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX;
   2042     const float content_width_headers = column->ContentMaxXHeadersIdeal - column->WorkMinX;
   2043     float width_auto = content_width_body;
   2044     if (!(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth))
   2045         width_auto = ImMax(width_auto, content_width_headers);
   2046 
   2047     // Non-resizable fixed columns preserve their requested width
   2048     if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f)
   2049         if (!(table->Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize))
   2050             width_auto = column->InitStretchWeightOrWidth;
   2051 
   2052     return ImMax(width_auto, table->MinColumnWidth);
   2053 }
   2054 
   2055 // 'width' = inner column width, without padding
   2056 void ImGui::TableSetColumnWidth(int column_n, float width)
   2057 {
   2058     ImGuiContext& g = *GImGui;
   2059     ImGuiTable* table = g.CurrentTable;
   2060     IM_ASSERT(table != NULL && table->IsLayoutLocked == false);
   2061     IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
   2062     ImGuiTableColumn* column_0 = &table->Columns[column_n];
   2063     float column_0_width = width;
   2064 
   2065     // Apply constraints early
   2066     // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded)
   2067     IM_ASSERT(table->MinColumnWidth > 0.0f);
   2068     const float min_width = table->MinColumnWidth;
   2069     const float max_width = ImMax(min_width, TableGetMaxColumnWidth(table, column_n));
   2070     column_0_width = ImClamp(column_0_width, min_width, max_width);
   2071     if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width)
   2072         return;
   2073 
   2074     //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width);
   2075     ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : NULL;
   2076 
   2077     // In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns.
   2078     // - All fixed: easy.
   2079     // - All stretch: easy.
   2080     // - One or more fixed + one stretch: easy.
   2081     // - One or more fixed + more than one stretch: tricky.
   2082     // Qt when manual resize is enabled only support a single _trailing_ stretch column.
   2083 
   2084     // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1.
   2085     // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user.
   2086     // Scenarios:
   2087     // - F1 F2 F3  resize from F1| or F2|   --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset.
   2088     // - F1 F2 F3  resize from F3|          --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered.
   2089     // - 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.
   2090     // - F1 F2 W3  resize from W3|          --> ok: no-op (disabled by Resize Rule 1)
   2091     // - W1 W2 W3  resize from W1| or W2|   --> ok
   2092     // - W1 W2 W3  resize from W3|          --> ok: no-op (disabled by Resize Rule 1)
   2093     // - W1 F2 F3  resize from F3|          --> ok: no-op (disabled by Resize Rule 1)
   2094     // - W1 F2     resize from F2|          --> ok: no-op (disabled by Resize Rule 1)
   2095     // - W1 W2 F3  resize from W1| or W2|   --> ok
   2096     // - W1 F2 W3  resize from W1| or F2|   --> ok
   2097     // - F1 W2 F3  resize from W2|          --> ok
   2098     // - F1 W3 F2  resize from W3|          --> ok
   2099     // - W1 F2 F3  resize from W1|          --> ok: equivalent to resizing |F2. F3 will not move.
   2100     // - W1 F2 F3  resize from F2|          --> ok
   2101     // All resizes from a Wx columns are locking other columns.
   2102 
   2103     // Possible improvements:
   2104     // - W1 W2 W3  resize W1|               --> to not be stuck, both W2 and W3 would stretch down. Seems possible to fix. Would be most beneficial to simplify resize of all-weighted columns.
   2105     // - W3 F1 F2  resize W3|               --> to not be stuck past F1|, both F1 and F2 would need to stretch down, which would be lossy or ambiguous. Seems hard to fix.
   2106 
   2107     // [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout().
   2108 
   2109     // If we have all Fixed columns OR resizing a Fixed column that doesn't come after a Stretch one, we can do an offsetting resize.
   2110     // This is the preferred resize path
   2111     if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed)
   2112         if (!column_1 || table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder >= column_0->DisplayOrder)
   2113         {
   2114             column_0->WidthRequest = column_0_width;
   2115             table->IsSettingsDirty = true;
   2116             return;
   2117         }
   2118 
   2119     // We can also use previous column if there's no next one (this is used when doing an auto-fit on the right-most stretch column)
   2120     if (column_1 == NULL)
   2121         column_1 = (column_0->PrevEnabledColumn != -1) ? &table->Columns[column_0->PrevEnabledColumn] : NULL;
   2122     if (column_1 == NULL)
   2123         return;
   2124 
   2125     // Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column.
   2126     // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)
   2127     float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width);
   2128     column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width;
   2129     IM_ASSERT(column_0_width > 0.0f && column_1_width > 0.0f);
   2130     column_0->WidthRequest = column_0_width;
   2131     column_1->WidthRequest = column_1_width;
   2132     if ((column_0->Flags | column_1->Flags) & ImGuiTableColumnFlags_WidthStretch)
   2133         TableUpdateColumnsWeightFromWidth(table);
   2134     table->IsSettingsDirty = true;
   2135 }
   2136 
   2137 // Disable clipping then auto-fit, will take 2 frames
   2138 // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns)
   2139 void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n)
   2140 {
   2141     // Single auto width uses auto-fit
   2142     ImGuiTableColumn* column = &table->Columns[column_n];
   2143     if (!column->IsEnabled)
   2144         return;
   2145     column->CannotSkipItemsQueue = (1 << 0);
   2146     table->AutoFitSingleColumn = (ImGuiTableColumnIdx)column_n;
   2147 }
   2148 
   2149 void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table)
   2150 {
   2151     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2152     {
   2153         ImGuiTableColumn* column = &table->Columns[column_n];
   2154         if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Cannot reset weight of hidden stretch column
   2155             continue;
   2156         column->CannotSkipItemsQueue = (1 << 0);
   2157         column->AutoFitQueue = (1 << 1);
   2158     }
   2159 }
   2160 
   2161 void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table)
   2162 {
   2163     IM_ASSERT(table->LeftMostStretchedColumn != -1 && table->RightMostStretchedColumn != -1);
   2164 
   2165     // Measure existing quantity
   2166     float visible_weight = 0.0f;
   2167     float visible_width = 0.0f;
   2168     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2169     {
   2170         ImGuiTableColumn* column = &table->Columns[column_n];
   2171         if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))
   2172             continue;
   2173         IM_ASSERT(column->StretchWeight > 0.0f);
   2174         visible_weight += column->StretchWeight;
   2175         visible_width += column->WidthRequest;
   2176     }
   2177     IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f);
   2178 
   2179     // Apply new weights
   2180     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2181     {
   2182         ImGuiTableColumn* column = &table->Columns[column_n];
   2183         if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))
   2184             continue;
   2185         column->StretchWeight = (column->WidthRequest / visible_width) * visible_weight;
   2186         IM_ASSERT(column->StretchWeight > 0.0f);
   2187     }
   2188 }
   2189 
   2190 //-------------------------------------------------------------------------
   2191 // [SECTION] Tables: Drawing
   2192 //-------------------------------------------------------------------------
   2193 // - TablePushBackgroundChannel() [Internal]
   2194 // - TablePopBackgroundChannel() [Internal]
   2195 // - TableSetupDrawChannels() [Internal]
   2196 // - TableMergeDrawChannels() [Internal]
   2197 // - TableDrawBorders() [Internal]
   2198 //-------------------------------------------------------------------------
   2199 
   2200 // Bg2 is used by Selectable (and possibly other widgets) to render to the background.
   2201 // Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect.
   2202 void ImGui::TablePushBackgroundChannel()
   2203 {
   2204     ImGuiContext& g = *GImGui;
   2205     ImGuiWindow* window = g.CurrentWindow;
   2206     ImGuiTable* table = g.CurrentTable;
   2207 
   2208     // Optimization: avoid SetCurrentChannel() + PushClipRect()
   2209     table->HostBackupInnerClipRect = window->ClipRect;
   2210     SetWindowClipRectBeforeSetChannel(window, table->Bg2ClipRectForDrawCmd);
   2211     table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent);
   2212 }
   2213 
   2214 void ImGui::TablePopBackgroundChannel()
   2215 {
   2216     ImGuiContext& g = *GImGui;
   2217     ImGuiWindow* window = g.CurrentWindow;
   2218     ImGuiTable* table = g.CurrentTable;
   2219     ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
   2220 
   2221     // Optimization: avoid PopClipRect() + SetCurrentChannel()
   2222     SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect);
   2223     table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
   2224 }
   2225 
   2226 // Allocate draw channels. Called by TableUpdateLayout()
   2227 // - We allocate them following storage order instead of display order so reordering columns won't needlessly
   2228 //   increase overall dormant memory cost.
   2229 // - We isolate headers draw commands in their own channels instead of just altering clip rects.
   2230 //   This is in order to facilitate merging of draw commands.
   2231 // - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels.
   2232 // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other
   2233 //   channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged.
   2234 // - We allocate 1 or 2 background draw channels. This is because we know TablePushBackgroundChannel() is only used for
   2235 //   horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4).
   2236 // Draw channel allocation (before merging):
   2237 // - NoClip                       --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call)
   2238 // - Clip                         --> 2+D+N channels
   2239 // - FreezeRows                   --> 2+D+N*2 (unless scrolling value is zero)
   2240 // - FreezeRows || FreezeColunns  --> 3+D+N*2 (unless scrolling value is zero)
   2241 // Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0.
   2242 void ImGui::TableSetupDrawChannels(ImGuiTable* table)
   2243 {
   2244     const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1;
   2245     const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount;
   2246     const int channels_for_bg = 1 + 1 * freeze_row_multiplier;
   2247     const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0;
   2248     const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy;
   2249     table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total);
   2250     table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1);
   2251     table->Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN;
   2252     table->Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN);
   2253 
   2254     int draw_channel_current = 2;
   2255     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2256     {
   2257         ImGuiTableColumn* column = &table->Columns[column_n];
   2258         if (column->IsVisibleX && column->IsVisibleY)
   2259         {
   2260             column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current);
   2261             column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0));
   2262             if (!(table->Flags & ImGuiTableFlags_NoClip))
   2263                 draw_channel_current++;
   2264         }
   2265         else
   2266         {
   2267             column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel;
   2268         }
   2269         column->DrawChannelCurrent = column->DrawChannelFrozen;
   2270     }
   2271 
   2272     // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default.
   2273     // All our cell highlight are manually clipped with BgClipRect. When unfreezing it will be made smaller to fit scrolling rect.
   2274     // (This technically isn't part of setting up draw channels, but is reasonably related to be done here)
   2275     table->BgClipRect = table->InnerClipRect;
   2276     table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect;
   2277     table->Bg2ClipRectForDrawCmd = table->HostClipRect;
   2278     IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y);
   2279 }
   2280 
   2281 // This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable().
   2282 // For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect,
   2283 // actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels().
   2284 //
   2285 // Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve
   2286 // this we merge their clip rect and make them contiguous in the channel list, so they can be merged
   2287 // by the call to DrawSplitter.Merge() following to the call to this function.
   2288 // We reorder draw commands by arranging them into a maximum of 4 distinct groups:
   2289 //
   2290 //   1 group:               2 groups:              2 groups:              4 groups:
   2291 //   [ 0. ] no freeze       [ 0. ] row freeze      [ 01 ] col freeze      [ 01 ] row+col freeze
   2292 //   [ .. ]  or no scroll   [ 2. ]  and v-scroll   [ .. ]  and h-scroll   [ 23 ]  and v+h-scroll
   2293 //
   2294 // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled).
   2295 // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group
   2296 // based on its position (within frozen rows/columns groups or not).
   2297 // At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect.
   2298 // This function assume that each column are pointing to a distinct draw channel,
   2299 // otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask.
   2300 //
   2301 // Column channels will not be merged into one of the 1-4 groups in the following cases:
   2302 // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value).
   2303 //   Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds
   2304 //   matches, by e.g. calling SetCursorScreenPos().
   2305 // - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here..
   2306 //   we could do better but it's going to be rare and probably not worth the hassle.
   2307 // Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd.
   2308 //
   2309 // This function is particularly tricky to understand.. take a breath.
   2310 void ImGui::TableMergeDrawChannels(ImGuiTable* table)
   2311 {
   2312     ImGuiContext& g = *GImGui;
   2313     ImDrawListSplitter* splitter = table->DrawSplitter;
   2314     const bool has_freeze_v = (table->FreezeRowsCount > 0);
   2315     const bool has_freeze_h = (table->FreezeColumnsCount > 0);
   2316     IM_ASSERT(splitter->_Current == 0);
   2317 
   2318     // Track which groups we are going to attempt to merge, and which channels goes into each group.
   2319     struct MergeGroup
   2320     {
   2321         ImRect  ClipRect;
   2322         int     ChannelsCount;
   2323         ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> ChannelsMask;
   2324 
   2325         MergeGroup() { ChannelsCount = 0; }
   2326     };
   2327     int merge_group_mask = 0x00;
   2328     MergeGroup merge_groups[4];
   2329 
   2330     // 1. Scan channels and take note of those which can be merged
   2331     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2332     {
   2333         if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0)
   2334             continue;
   2335         ImGuiTableColumn* column = &table->Columns[column_n];
   2336 
   2337         const int merge_group_sub_count = has_freeze_v ? 2 : 1;
   2338         for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++)
   2339         {
   2340             const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen;
   2341 
   2342             // Don't attempt to merge if there are multiple draw calls within the column
   2343             ImDrawChannel* src_channel = &splitter->_Channels[channel_no];
   2344             if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0)
   2345                 src_channel->_CmdBuffer.pop_back();
   2346             if (src_channel->_CmdBuffer.Size != 1)
   2347                 continue;
   2348 
   2349             // Find out the width of this merge group and check if it will fit in our column
   2350             // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it)
   2351             if (!(column->Flags & ImGuiTableColumnFlags_NoClip))
   2352             {
   2353                 float content_max_x;
   2354                 if (!has_freeze_v)
   2355                     content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze
   2356                 else if (merge_group_sub_n == 0)
   2357                     content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed);   // Row freeze: use width before freeze
   2358                 else
   2359                     content_max_x = column->ContentMaxXUnfrozen;                                        // Row freeze: use width after freeze
   2360                 if (content_max_x > column->ClipRect.Max.x)
   2361                     continue;
   2362             }
   2363 
   2364             const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2);
   2365             IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS);
   2366             MergeGroup* merge_group = &merge_groups[merge_group_n];
   2367             if (merge_group->ChannelsCount == 0)
   2368                 merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
   2369             merge_group->ChannelsMask.SetBit(channel_no);
   2370             merge_group->ChannelsCount++;
   2371             merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect);
   2372             merge_group_mask |= (1 << merge_group_n);
   2373         }
   2374 
   2375         // Invalidate current draw channel
   2376         // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data)
   2377         column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx)-1;
   2378     }
   2379 
   2380     // [DEBUG] Display merge groups
   2381 #if 0
   2382     if (g.IO.KeyShift)
   2383         for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++)
   2384         {
   2385             MergeGroup* merge_group = &merge_groups[merge_group_n];
   2386             if (merge_group->ChannelsCount == 0)
   2387                 continue;
   2388             char buf[32];
   2389             ImFormatString(buf, 32, "MG%d:%d", merge_group_n, merge_group->ChannelsCount);
   2390             ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4);
   2391             ImVec2 text_size = CalcTextSize(buf, NULL);
   2392             GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255));
   2393             GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL);
   2394             GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255));
   2395         }
   2396 #endif
   2397 
   2398     // 2. Rewrite channel list in our preferred order
   2399     if (merge_group_mask != 0)
   2400     {
   2401         // We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels().
   2402         const int LEADING_DRAW_CHANNELS = 2;
   2403         g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized
   2404         ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data;
   2405         ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> remaining_mask;                       // We need 132-bit of storage
   2406         remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count);
   2407         remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen);
   2408         IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN);
   2409         int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS);
   2410         //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect;
   2411         ImRect host_rect = table->HostClipRect;
   2412         for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++)
   2413         {
   2414             if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount)
   2415             {
   2416                 MergeGroup* merge_group = &merge_groups[merge_group_n];
   2417                 ImRect merge_clip_rect = merge_group->ClipRect;
   2418 
   2419                 // Extend outer-most clip limits to match those of host, so draw calls can be merged even if
   2420                 // outer-most columns have some outer padding offsetting them from their parent ClipRect.
   2421                 // The principal cases this is dealing with are:
   2422                 // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge
   2423                 // - 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
   2424                 // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit
   2425                 // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect.
   2426                 if ((merge_group_n & 1) == 0 || !has_freeze_h)
   2427                     merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x);
   2428                 if ((merge_group_n & 2) == 0 || !has_freeze_v)
   2429                     merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y);
   2430                 if ((merge_group_n & 1) != 0)
   2431                     merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x);
   2432                 if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0)
   2433                     merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y);
   2434 #if 0
   2435                 GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f);
   2436                 GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200));
   2437                 GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200));
   2438 #endif
   2439                 remaining_count -= merge_group->ChannelsCount;
   2440                 for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++)
   2441                     remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n];
   2442                 for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++)
   2443                 {
   2444                     // Copy + overwrite new clip rect
   2445                     if (!merge_group->ChannelsMask.TestBit(n))
   2446                         continue;
   2447                     merge_group->ChannelsMask.ClearBit(n);
   2448                     merge_channels_count--;
   2449 
   2450                     ImDrawChannel* channel = &splitter->_Channels[n];
   2451                     IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect)));
   2452                     channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4();
   2453                     memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));
   2454                 }
   2455             }
   2456 
   2457             // Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1)
   2458             if (merge_group_n == 1 && has_freeze_v)
   2459                 memcpy(dst_tmp++, &splitter->_Channels[table->Bg2DrawChannelUnfrozen], sizeof(ImDrawChannel));
   2460         }
   2461 
   2462         // Append unmergeable channels that we didn't reorder at the end of the list
   2463         for (int n = 0; n < splitter->_Count && remaining_count != 0; n++)
   2464         {
   2465             if (!remaining_mask.TestBit(n))
   2466                 continue;
   2467             ImDrawChannel* channel = &splitter->_Channels[n];
   2468             memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));
   2469             remaining_count--;
   2470         }
   2471         IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size);
   2472         memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel));
   2473     }
   2474 }
   2475 
   2476 // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow)
   2477 void ImGui::TableDrawBorders(ImGuiTable* table)
   2478 {
   2479     ImGuiWindow* inner_window = table->InnerWindow;
   2480     if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect))
   2481         return;
   2482 
   2483     ImDrawList* inner_drawlist = inner_window->DrawList;
   2484     table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0);
   2485     inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false);
   2486 
   2487     // Draw inner border and resizing feedback
   2488     const float border_size = TABLE_BORDER_SIZE;
   2489     const float draw_y1 = table->InnerRect.Min.y;
   2490     const float draw_y2_body = table->InnerRect.Max.y;
   2491     const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->LastFirstRowHeight) : draw_y1;
   2492     if (table->Flags & ImGuiTableFlags_BordersInnerV)
   2493     {
   2494         for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
   2495         {
   2496             if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
   2497                 continue;
   2498 
   2499             const int column_n = table->DisplayOrderToIndex[order_n];
   2500             ImGuiTableColumn* column = &table->Columns[column_n];
   2501             const bool is_hovered = (table->HoveredColumnBorder == column_n);
   2502             const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent);
   2503             const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0;
   2504             const bool is_frozen_separator = (table->FreezeColumnsCount != -1 && table->FreezeColumnsCount == order_n + 1);
   2505             if (column->MaxX > table->InnerClipRect.Max.x && !is_resized)
   2506                 continue;
   2507 
   2508             // Decide whether right-most column is visible
   2509             if (column->NextEnabledColumn == -1 && !is_resizable)
   2510                 if ((table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame || (table->Flags & ImGuiTableFlags_NoHostExtendX))
   2511                     continue;
   2512             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..
   2513                 continue;
   2514 
   2515             // Draw in outer window so right-most column won't be clipped
   2516             // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling.
   2517             ImU32 col;
   2518             float draw_y2;
   2519             if (is_hovered || is_resized || is_frozen_separator)
   2520             {
   2521                 draw_y2 = draw_y2_body;
   2522                 col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : table->BorderColorStrong;
   2523             }
   2524             else
   2525             {
   2526                 draw_y2 = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body;
   2527                 col = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? table->BorderColorStrong : table->BorderColorLight;
   2528             }
   2529 
   2530             if (draw_y2 > draw_y1)
   2531                 inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size);
   2532         }
   2533     }
   2534 
   2535     // Draw outer border
   2536     // FIXME: could use AddRect or explicit VLine/HLine helper?
   2537     if (table->Flags & ImGuiTableFlags_BordersOuter)
   2538     {
   2539         // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call
   2540         // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their
   2541         // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part
   2542         // of it in inner window, and the part that's over scrollbars in the outer window..)
   2543         // Either solution currently won't allow us to use a larger border size: the border would clipped.
   2544         const ImRect outer_border = table->OuterRect;
   2545         const ImU32 outer_col = table->BorderColorStrong;
   2546         if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter)
   2547         {
   2548             inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size);
   2549         }
   2550         else if (table->Flags & ImGuiTableFlags_BordersOuterV)
   2551         {
   2552             inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size);
   2553             inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size);
   2554         }
   2555         else if (table->Flags & ImGuiTableFlags_BordersOuterH)
   2556         {
   2557             inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size);
   2558             inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size);
   2559         }
   2560     }
   2561     if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y)
   2562     {
   2563         // Draw bottom-most row border
   2564         const float border_y = table->RowPosY2;
   2565         if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y)
   2566             inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size);
   2567     }
   2568 
   2569     inner_drawlist->PopClipRect();
   2570 }
   2571 
   2572 //-------------------------------------------------------------------------
   2573 // [SECTION] Tables: Sorting
   2574 //-------------------------------------------------------------------------
   2575 // - TableGetSortSpecs()
   2576 // - TableFixColumnSortDirection() [Internal]
   2577 // - TableGetColumnNextSortDirection() [Internal]
   2578 // - TableSetColumnSortDirection() [Internal]
   2579 // - TableSortSpecsSanitize() [Internal]
   2580 // - TableSortSpecsBuild() [Internal]
   2581 //-------------------------------------------------------------------------
   2582 
   2583 // Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set)
   2584 // You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since
   2585 // last call, or the first time.
   2586 // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()!
   2587 ImGuiTableSortSpecs* ImGui::TableGetSortSpecs()
   2588 {
   2589     ImGuiContext& g = *GImGui;
   2590     ImGuiTable* table = g.CurrentTable;
   2591     IM_ASSERT(table != NULL);
   2592 
   2593     if (!(table->Flags & ImGuiTableFlags_Sortable))
   2594         return NULL;
   2595 
   2596     // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths.
   2597     if (!table->IsLayoutLocked)
   2598         TableUpdateLayout(table);
   2599 
   2600     if (table->IsSortSpecsDirty)
   2601         TableSortSpecsBuild(table);
   2602 
   2603     return &table->SortSpecs;
   2604 }
   2605 
   2606 static inline ImGuiSortDirection TableGetColumnAvailSortDirection(ImGuiTableColumn* column, int n)
   2607 {
   2608     IM_ASSERT(n < column->SortDirectionsAvailCount);
   2609     return (column->SortDirectionsAvailList >> (n << 1)) & 0x03;
   2610 }
   2611 
   2612 // Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending)
   2613 void ImGui::TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column)
   2614 {
   2615     if (column->SortOrder == -1 || (column->SortDirectionsAvailMask & (1 << column->SortDirection)) != 0)
   2616         return;
   2617     column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0);
   2618     table->IsSortSpecsDirty = true;
   2619 }
   2620 
   2621 // Calculate next sort direction that would be set after clicking the column
   2622 // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click.
   2623 // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op.
   2624 IM_STATIC_ASSERT(ImGuiSortDirection_None == 0 && ImGuiSortDirection_Ascending == 1 && ImGuiSortDirection_Descending == 2);
   2625 ImGuiSortDirection ImGui::TableGetColumnNextSortDirection(ImGuiTableColumn* column)
   2626 {
   2627     IM_ASSERT(column->SortDirectionsAvailCount > 0);
   2628     if (column->SortOrder == -1)
   2629         return TableGetColumnAvailSortDirection(column, 0);
   2630     for (int n = 0; n < 3; n++)
   2631         if (column->SortDirection == TableGetColumnAvailSortDirection(column, n))
   2632             return TableGetColumnAvailSortDirection(column, (n + 1) % column->SortDirectionsAvailCount);
   2633     IM_ASSERT(0);
   2634     return ImGuiSortDirection_None;
   2635 }
   2636 
   2637 // Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert
   2638 // the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code.
   2639 void ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs)
   2640 {
   2641     ImGuiContext& g = *GImGui;
   2642     ImGuiTable* table = g.CurrentTable;
   2643 
   2644     if (!(table->Flags & ImGuiTableFlags_SortMulti))
   2645         append_to_sort_specs = false;
   2646     if (!(table->Flags & ImGuiTableFlags_SortTristate))
   2647         IM_ASSERT(sort_direction != ImGuiSortDirection_None);
   2648 
   2649     ImGuiTableColumnIdx sort_order_max = 0;
   2650     if (append_to_sort_specs)
   2651         for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)
   2652             sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder);
   2653 
   2654     ImGuiTableColumn* column = &table->Columns[column_n];
   2655     column->SortDirection = (ImU8)sort_direction;
   2656     if (column->SortDirection == ImGuiSortDirection_None)
   2657         column->SortOrder = -1;
   2658     else if (column->SortOrder == -1 || !append_to_sort_specs)
   2659         column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0;
   2660 
   2661     for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)
   2662     {
   2663         ImGuiTableColumn* other_column = &table->Columns[other_column_n];
   2664         if (other_column != column && !append_to_sort_specs)
   2665             other_column->SortOrder = -1;
   2666         TableFixColumnSortDirection(table, other_column);
   2667     }
   2668     table->IsSettingsDirty = true;
   2669     table->IsSortSpecsDirty = true;
   2670 }
   2671 
   2672 void ImGui::TableSortSpecsSanitize(ImGuiTable* table)
   2673 {
   2674     IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable);
   2675 
   2676     // Clear SortOrder from hidden column and verify that there's no gap or duplicate.
   2677     int sort_order_count = 0;
   2678     ImU64 sort_order_mask = 0x00;
   2679     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2680     {
   2681         ImGuiTableColumn* column = &table->Columns[column_n];
   2682         if (column->SortOrder != -1 && !column->IsEnabled)
   2683             column->SortOrder = -1;
   2684         if (column->SortOrder == -1)
   2685             continue;
   2686         sort_order_count++;
   2687         sort_order_mask |= ((ImU64)1 << column->SortOrder);
   2688         IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8);
   2689     }
   2690 
   2691     const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1);
   2692     const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_SortMulti);
   2693     if (need_fix_linearize || need_fix_single_sort_order)
   2694     {
   2695         ImU64 fixed_mask = 0x00;
   2696         for (int sort_n = 0; sort_n < sort_order_count; sort_n++)
   2697         {
   2698             // Fix: Rewrite sort order fields if needed so they have no gap or duplicate.
   2699             // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1)
   2700             int column_with_smallest_sort_order = -1;
   2701             for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2702                 if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1)
   2703                     if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder)
   2704                         column_with_smallest_sort_order = column_n;
   2705             IM_ASSERT(column_with_smallest_sort_order != -1);
   2706             fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order);
   2707             table->Columns[column_with_smallest_sort_order].SortOrder = (ImGuiTableColumnIdx)sort_n;
   2708 
   2709             // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set.
   2710             if (need_fix_single_sort_order)
   2711             {
   2712                 sort_order_count = 1;
   2713                 for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2714                     if (column_n != column_with_smallest_sort_order)
   2715                         table->Columns[column_n].SortOrder = -1;
   2716                 break;
   2717             }
   2718         }
   2719     }
   2720 
   2721     // Fallback default sort order (if no column had the ImGuiTableColumnFlags_DefaultSort flag)
   2722     if (sort_order_count == 0 && !(table->Flags & ImGuiTableFlags_SortTristate))
   2723         for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2724         {
   2725             ImGuiTableColumn* column = &table->Columns[column_n];
   2726             if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort))
   2727             {
   2728                 sort_order_count = 1;
   2729                 column->SortOrder = 0;
   2730                 column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0);
   2731                 break;
   2732             }
   2733         }
   2734 
   2735     table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count;
   2736 }
   2737 
   2738 void ImGui::TableSortSpecsBuild(ImGuiTable* table)
   2739 {
   2740     IM_ASSERT(table->IsSortSpecsDirty);
   2741     TableSortSpecsSanitize(table);
   2742 
   2743     // Write output
   2744     ImGuiTableTempData* temp_data = table->TempData;
   2745     temp_data->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount);
   2746     ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &temp_data->SortSpecsSingle : temp_data->SortSpecsMulti.Data;
   2747     if (sort_specs != NULL)
   2748         for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   2749         {
   2750             ImGuiTableColumn* column = &table->Columns[column_n];
   2751             if (column->SortOrder == -1)
   2752                 continue;
   2753             IM_ASSERT(column->SortOrder < table->SortSpecsCount);
   2754             ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder];
   2755             sort_spec->ColumnUserID = column->UserID;
   2756             sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n;
   2757             sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder;
   2758             sort_spec->SortDirection = column->SortDirection;
   2759         }
   2760     table->SortSpecs.Specs = sort_specs;
   2761     table->SortSpecs.SpecsCount = table->SortSpecsCount;
   2762     table->SortSpecs.SpecsDirty = true; // Mark as dirty for user
   2763     table->IsSortSpecsDirty = false; // Mark as not dirty for us
   2764 }
   2765 
   2766 //-------------------------------------------------------------------------
   2767 // [SECTION] Tables: Headers
   2768 //-------------------------------------------------------------------------
   2769 // - TableGetHeaderRowHeight() [Internal]
   2770 // - TableHeadersRow()
   2771 // - TableHeader()
   2772 //-------------------------------------------------------------------------
   2773 
   2774 float ImGui::TableGetHeaderRowHeight()
   2775 {
   2776     // Caring for a minor edge case:
   2777     // Calculate row height, for the unlikely case that some labels may be taller than others.
   2778     // If we didn't do that, uneven header height would highlight but smaller one before the tallest wouldn't catch input for all height.
   2779     // In your custom header row you may omit this all together and just call TableNextRow() without a height...
   2780     float row_height = GetTextLineHeight();
   2781     int columns_count = TableGetColumnCount();
   2782     for (int column_n = 0; column_n < columns_count; column_n++)
   2783         if (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_IsEnabled)
   2784             row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y);
   2785     row_height += GetStyle().CellPadding.y * 2.0f;
   2786     return row_height;
   2787 }
   2788 
   2789 // [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn().
   2790 // The intent is that advanced users willing to create customized headers would not need to use this helper
   2791 // and can create their own! For example: TableHeader() may be preceeded by Checkbox() or other custom widgets.
   2792 // See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this.
   2793 // This code is constructed to not make much use of internal functions, as it is intended to be a template to copy.
   2794 // FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public.
   2795 void ImGui::TableHeadersRow()
   2796 {
   2797     ImGuiContext& g = *GImGui;
   2798     ImGuiTable* table = g.CurrentTable;
   2799     IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!");
   2800 
   2801     // Layout if not already done (this is automatically done by TableNextRow, we do it here solely to facilitate stepping in debugger as it is frequent to step in TableUpdateLayout)
   2802     if (!table->IsLayoutLocked)
   2803         TableUpdateLayout(table);
   2804 
   2805     // Open row
   2806     const float row_y1 = GetCursorScreenPos().y;
   2807     const float row_height = TableGetHeaderRowHeight();
   2808     TableNextRow(ImGuiTableRowFlags_Headers, row_height);
   2809     if (table->HostSkipItems) // Merely an optimization, you may skip in your own code.
   2810         return;
   2811 
   2812     const int columns_count = TableGetColumnCount();
   2813     for (int column_n = 0; column_n < columns_count; column_n++)
   2814     {
   2815         if (!TableSetColumnIndex(column_n))
   2816             continue;
   2817 
   2818         // Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them)
   2819         // - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide
   2820         // - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier.
   2821         const char* name = TableGetColumnName(column_n);
   2822         PushID(table->InstanceCurrent * table->ColumnsCount + column_n);
   2823         TableHeader(name);
   2824         PopID();
   2825     }
   2826 
   2827     // Allow opening popup from the right-most section after the last column.
   2828     ImVec2 mouse_pos = ImGui::GetMousePos();
   2829     if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count)
   2830         if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height)
   2831             TableOpenContextMenu(-1); // Will open a non-column-specific popup.
   2832 }
   2833 
   2834 // Emit a column header (text + optional sort order)
   2835 // We cpu-clip text here so that all columns headers can be merged into a same draw call.
   2836 // Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader()
   2837 void ImGui::TableHeader(const char* label)
   2838 {
   2839     ImGuiContext& g = *GImGui;
   2840     ImGuiWindow* window = g.CurrentWindow;
   2841     if (window->SkipItems)
   2842         return;
   2843 
   2844     ImGuiTable* table = g.CurrentTable;
   2845     IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!");
   2846     IM_ASSERT(table->CurrentColumn != -1);
   2847     const int column_n = table->CurrentColumn;
   2848     ImGuiTableColumn* column = &table->Columns[column_n];
   2849 
   2850     // Label
   2851     if (label == NULL)
   2852         label = "";
   2853     const char* label_end = FindRenderedTextEnd(label);
   2854     ImVec2 label_size = CalcTextSize(label, label_end, true);
   2855     ImVec2 label_pos = window->DC.CursorPos;
   2856 
   2857     // If we already got a row height, there's use that.
   2858     // FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect?
   2859     ImRect cell_r = TableGetCellBgRect(table, column_n);
   2860     float label_height = ImMax(label_size.y, table->RowMinHeight - table->CellPaddingY * 2.0f);
   2861 
   2862     // Calculate ideal size for sort order arrow
   2863     float w_arrow = 0.0f;
   2864     float w_sort_text = 0.0f;
   2865     char sort_order_suf[4] = "";
   2866     const float ARROW_SCALE = 0.65f;
   2867     if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort))
   2868     {
   2869         w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);
   2870         if (column->SortOrder > 0)
   2871         {
   2872             ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1);
   2873             w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x;
   2874         }
   2875     }
   2876 
   2877     // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging.
   2878     float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow;
   2879     column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, column->WorkMaxX);
   2880     column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x);
   2881 
   2882     // Keep header highlighted when context menu is open.
   2883     const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent);
   2884     ImGuiID id = window->GetID(label);
   2885     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));
   2886     ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal
   2887     if (!ItemAdd(bb, id))
   2888         return;
   2889 
   2890     //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
   2891     //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
   2892 
   2893     // Using AllowItemOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items.
   2894     bool hovered, held;
   2895     bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_AllowItemOverlap);
   2896     if (g.ActiveId != id)
   2897         SetItemAllowOverlap();
   2898     if (held || hovered || selected)
   2899     {
   2900         const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
   2901         //RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
   2902         TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn);
   2903         RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
   2904     }
   2905     else
   2906     {
   2907         // Submit single cell bg color in the case we didn't submit a full header row
   2908         if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0)
   2909             TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn);
   2910     }
   2911     if (held)
   2912         table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n;
   2913     window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f;
   2914 
   2915     // Drag and drop to re-order columns.
   2916     // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone.
   2917     if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive)
   2918     {
   2919         // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x
   2920         table->ReorderColumn = (ImGuiTableColumnIdx)column_n;
   2921         table->InstanceInteracted = table->InstanceCurrent;
   2922 
   2923         // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder.
   2924         if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x)
   2925             if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL)
   2926                 if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder))
   2927                     if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < table->FreezeColumnsRequest))
   2928                         table->ReorderColumnDir = -1;
   2929         if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x)
   2930             if (ImGuiTableColumn* next_column = (column->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL)
   2931                 if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder))
   2932                     if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest))
   2933                         table->ReorderColumnDir = +1;
   2934     }
   2935 
   2936     // Sort order arrow
   2937     const float ellipsis_max = cell_r.Max.x - w_arrow - w_sort_text;
   2938     if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort))
   2939     {
   2940         if (column->SortOrder != -1)
   2941         {
   2942             float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text);
   2943             float y = label_pos.y;
   2944             if (column->SortOrder > 0)
   2945             {
   2946                 PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f));
   2947                 RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf);
   2948                 PopStyleColor();
   2949                 x += w_sort_text;
   2950             }
   2951             RenderArrow(window->DrawList, ImVec2(x, y), GetColorU32(ImGuiCol_Text), column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE);
   2952         }
   2953 
   2954         // Handle clicking on column header to adjust Sort Order
   2955         if (pressed && table->ReorderColumn != column_n)
   2956         {
   2957             ImGuiSortDirection sort_direction = TableGetColumnNextSortDirection(column);
   2958             TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift);
   2959         }
   2960     }
   2961 
   2962     // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will
   2963     // be merged into a single draw call.
   2964     //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE);
   2965     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);
   2966 
   2967     const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x);
   2968     if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay)
   2969         SetTooltip("%.*s", (int)(label_end - label), label);
   2970 
   2971     // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden
   2972     if (IsMouseReleased(1) && IsItemHovered())
   2973         TableOpenContextMenu(column_n);
   2974 }
   2975 
   2976 //-------------------------------------------------------------------------
   2977 // [SECTION] Tables: Context Menu
   2978 //-------------------------------------------------------------------------
   2979 // - TableOpenContextMenu() [Internal]
   2980 // - TableDrawContextMenu() [Internal]
   2981 //-------------------------------------------------------------------------
   2982 
   2983 // Use -1 to open menu not specific to a given column.
   2984 void ImGui::TableOpenContextMenu(int column_n)
   2985 {
   2986     ImGuiContext& g = *GImGui;
   2987     ImGuiTable* table = g.CurrentTable;
   2988     if (column_n == -1 && table->CurrentColumn != -1)   // When called within a column automatically use this one (for consistency)
   2989         column_n = table->CurrentColumn;
   2990     if (column_n == table->ColumnsCount)                // To facilitate using with TableGetHoveredColumn()
   2991         column_n = -1;
   2992     IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount);
   2993     if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable))
   2994     {
   2995         table->IsContextPopupOpen = true;
   2996         table->ContextPopupColumn = (ImGuiTableColumnIdx)column_n;
   2997         table->InstanceInteracted = table->InstanceCurrent;
   2998         const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID);
   2999         OpenPopupEx(context_menu_id, ImGuiPopupFlags_None);
   3000     }
   3001 }
   3002 
   3003 // Output context menu into current window (generally a popup)
   3004 // FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data?
   3005 void ImGui::TableDrawContextMenu(ImGuiTable* table)
   3006 {
   3007     ImGuiContext& g = *GImGui;
   3008     ImGuiWindow* window = g.CurrentWindow;
   3009     if (window->SkipItems)
   3010         return;
   3011 
   3012     bool want_separator = false;
   3013     const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1;
   3014     ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL;
   3015 
   3016     // Sizing
   3017     if (table->Flags & ImGuiTableFlags_Resizable)
   3018     {
   3019         if (column != NULL)
   3020         {
   3021             const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled;
   3022             if (MenuItem("Size column to fit###SizeOne", NULL, false, can_resize))
   3023                 TableSetColumnWidthAutoSingle(table, column_n);
   3024         }
   3025 
   3026         const char* size_all_desc;
   3027         if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame)
   3028             size_all_desc = "Size all columns to fit###SizeAll";        // All fixed
   3029         else
   3030             size_all_desc = "Size all columns to default###SizeAll";    // All stretch or mixed
   3031         if (MenuItem(size_all_desc, NULL))
   3032             TableSetColumnWidthAutoAll(table);
   3033         want_separator = true;
   3034     }
   3035 
   3036     // Ordering
   3037     if (table->Flags & ImGuiTableFlags_Reorderable)
   3038     {
   3039         if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder))
   3040             table->IsResetDisplayOrderRequest = true;
   3041         want_separator = true;
   3042     }
   3043 
   3044     // Reset all (should work but seems unnecessary/noisy to expose?)
   3045     //if (MenuItem("Reset all"))
   3046     //    table->IsResetAllRequest = true;
   3047 
   3048     // Sorting
   3049     // (modify TableOpenContextMenu() to add _Sortable flag if enabling this)
   3050 #if 0
   3051     if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0)
   3052     {
   3053         if (want_separator)
   3054             Separator();
   3055         want_separator = true;
   3056 
   3057         bool append_to_sort_specs = g.IO.KeyShift;
   3058         if (MenuItem("Sort in Ascending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0))
   3059             TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs);
   3060         if (MenuItem("Sort in Descending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0))
   3061             TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs);
   3062     }
   3063 #endif
   3064 
   3065     // Hiding / Visibility
   3066     if (table->Flags & ImGuiTableFlags_Hideable)
   3067     {
   3068         if (want_separator)
   3069             Separator();
   3070         want_separator = true;
   3071 
   3072         PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true);
   3073         for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)
   3074         {
   3075             ImGuiTableColumn* other_column = &table->Columns[other_column_n];
   3076             const char* name = TableGetColumnName(table, other_column_n);
   3077             if (name == NULL || name[0] == 0)
   3078                 name = "<Unknown>";
   3079 
   3080             // Make sure we can't hide the last active column
   3081             bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true;
   3082             if (other_column->IsEnabled && table->ColumnsEnabledCount <= 1)
   3083                 menu_item_active = false;
   3084             if (MenuItem(name, NULL, other_column->IsEnabled, menu_item_active))
   3085                 other_column->IsEnabledNextFrame = !other_column->IsEnabled;
   3086         }
   3087         PopItemFlag();
   3088     }
   3089 }
   3090 
   3091 //-------------------------------------------------------------------------
   3092 // [SECTION] Tables: Settings (.ini data)
   3093 //-------------------------------------------------------------------------
   3094 // FIXME: The binding/finding/creating flow are too confusing.
   3095 //-------------------------------------------------------------------------
   3096 // - TableSettingsInit() [Internal]
   3097 // - TableSettingsCalcChunkSize() [Internal]
   3098 // - TableSettingsCreate() [Internal]
   3099 // - TableSettingsFindByID() [Internal]
   3100 // - TableGetBoundSettings() [Internal]
   3101 // - TableResetSettings()
   3102 // - TableSaveSettings() [Internal]
   3103 // - TableLoadSettings() [Internal]
   3104 // - TableSettingsHandler_ClearAll() [Internal]
   3105 // - TableSettingsHandler_ApplyAll() [Internal]
   3106 // - TableSettingsHandler_ReadOpen() [Internal]
   3107 // - TableSettingsHandler_ReadLine() [Internal]
   3108 // - TableSettingsHandler_WriteAll() [Internal]
   3109 // - TableSettingsInstallHandler() [Internal]
   3110 //-------------------------------------------------------------------------
   3111 // [Init] 1: TableSettingsHandler_ReadXXXX()   Load and parse .ini file into TableSettings.
   3112 // [Main] 2: TableLoadSettings()               When table is created, bind Table to TableSettings, serialize TableSettings data into Table.
   3113 // [Main] 3: TableSaveSettings()               When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty.
   3114 // [Main] 4: TableSettingsHandler_WriteAll()   When .ini file is dirty (which can come from other source), save TableSettings into .ini file.
   3115 //-------------------------------------------------------------------------
   3116 
   3117 // Clear and initialize empty settings instance
   3118 static void TableSettingsInit(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max)
   3119 {
   3120     IM_PLACEMENT_NEW(settings) ImGuiTableSettings();
   3121     ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings();
   3122     for (int n = 0; n < columns_count_max; n++, settings_column++)
   3123         IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings();
   3124     settings->ID = id;
   3125     settings->ColumnsCount = (ImGuiTableColumnIdx)columns_count;
   3126     settings->ColumnsCountMax = (ImGuiTableColumnIdx)columns_count_max;
   3127     settings->WantApply = true;
   3128 }
   3129 
   3130 static size_t TableSettingsCalcChunkSize(int columns_count)
   3131 {
   3132     return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings);
   3133 }
   3134 
   3135 ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count)
   3136 {
   3137     ImGuiContext& g = *GImGui;
   3138     ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count));
   3139     TableSettingsInit(settings, id, columns_count, columns_count);
   3140     return settings;
   3141 }
   3142 
   3143 // Find existing settings
   3144 ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id)
   3145 {
   3146     // FIXME-OPT: Might want to store a lookup map for this?
   3147     ImGuiContext& g = *GImGui;
   3148     for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
   3149         if (settings->ID == id)
   3150             return settings;
   3151     return NULL;
   3152 }
   3153 
   3154 // Get settings for a given table, NULL if none
   3155 ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table)
   3156 {
   3157     if (table->SettingsOffset != -1)
   3158     {
   3159         ImGuiContext& g = *GImGui;
   3160         ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset);
   3161         IM_ASSERT(settings->ID == table->ID);
   3162         if (settings->ColumnsCountMax >= table->ColumnsCount)
   3163             return settings; // OK
   3164         settings->ID = 0; // Invalidate storage, we won't fit because of a count change
   3165     }
   3166     return NULL;
   3167 }
   3168 
   3169 // Restore initial state of table (with or without saved settings)
   3170 void ImGui::TableResetSettings(ImGuiTable* table)
   3171 {
   3172     table->IsInitializing = table->IsSettingsDirty = true;
   3173     table->IsResetAllRequest = false;
   3174     table->IsSettingsRequestLoad = false;                   // Don't reload from ini
   3175     table->SettingsLoadedFlags = ImGuiTableFlags_None;      // Mark as nothing loaded so our initialized data becomes authoritative
   3176 }
   3177 
   3178 void ImGui::TableSaveSettings(ImGuiTable* table)
   3179 {
   3180     table->IsSettingsDirty = false;
   3181     if (table->Flags & ImGuiTableFlags_NoSavedSettings)
   3182         return;
   3183 
   3184     // Bind or create settings data
   3185     ImGuiContext& g = *GImGui;
   3186     ImGuiTableSettings* settings = TableGetBoundSettings(table);
   3187     if (settings == NULL)
   3188     {
   3189         settings = TableSettingsCreate(table->ID, table->ColumnsCount);
   3190         table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings);
   3191     }
   3192     settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount;
   3193 
   3194     // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings
   3195     IM_ASSERT(settings->ID == table->ID);
   3196     IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount);
   3197     ImGuiTableColumn* column = table->Columns.Data;
   3198     ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
   3199 
   3200     bool save_ref_scale = false;
   3201     settings->SaveFlags = ImGuiTableFlags_None;
   3202     for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++)
   3203     {
   3204         const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest;
   3205         column_settings->WidthOrWeight = width_or_weight;
   3206         column_settings->Index = (ImGuiTableColumnIdx)n;
   3207         column_settings->DisplayOrder = column->DisplayOrder;
   3208         column_settings->SortOrder = column->SortOrder;
   3209         column_settings->SortDirection = column->SortDirection;
   3210         column_settings->IsEnabled = column->IsEnabled;
   3211         column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0;
   3212         if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0)
   3213             save_ref_scale = true;
   3214 
   3215         // We skip saving some data in the .ini file when they are unnecessary to restore our state.
   3216         // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f.
   3217         // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present.
   3218         if (width_or_weight != column->InitStretchWeightOrWidth)
   3219             settings->SaveFlags |= ImGuiTableFlags_Resizable;
   3220         if (column->DisplayOrder != n)
   3221             settings->SaveFlags |= ImGuiTableFlags_Reorderable;
   3222         if (column->SortOrder != -1)
   3223             settings->SaveFlags |= ImGuiTableFlags_Sortable;
   3224         if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0))
   3225             settings->SaveFlags |= ImGuiTableFlags_Hideable;
   3226     }
   3227     settings->SaveFlags &= table->Flags;
   3228     settings->RefScale = save_ref_scale ? table->RefScale : 0.0f;
   3229 
   3230     MarkIniSettingsDirty();
   3231 }
   3232 
   3233 void ImGui::TableLoadSettings(ImGuiTable* table)
   3234 {
   3235     ImGuiContext& g = *GImGui;
   3236     table->IsSettingsRequestLoad = false;
   3237     if (table->Flags & ImGuiTableFlags_NoSavedSettings)
   3238         return;
   3239 
   3240     // Bind settings
   3241     ImGuiTableSettings* settings;
   3242     if (table->SettingsOffset == -1)
   3243     {
   3244         settings = TableSettingsFindByID(table->ID);
   3245         if (settings == NULL)
   3246             return;
   3247         if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return...
   3248             table->IsSettingsDirty = true;
   3249         table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings);
   3250     }
   3251     else
   3252     {
   3253         settings = TableGetBoundSettings(table);
   3254     }
   3255 
   3256     table->SettingsLoadedFlags = settings->SaveFlags;
   3257     table->RefScale = settings->RefScale;
   3258 
   3259     // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
   3260     ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
   3261     ImU64 display_order_mask = 0;
   3262     for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++)
   3263     {
   3264         int column_n = column_settings->Index;
   3265         if (column_n < 0 || column_n >= table->ColumnsCount)
   3266             continue;
   3267 
   3268         ImGuiTableColumn* column = &table->Columns[column_n];
   3269         if (settings->SaveFlags & ImGuiTableFlags_Resizable)
   3270         {
   3271             if (column_settings->IsStretch)
   3272                 column->StretchWeight = column_settings->WidthOrWeight;
   3273             else
   3274                 column->WidthRequest = column_settings->WidthOrWeight;
   3275             column->AutoFitQueue = 0x00;
   3276         }
   3277         if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
   3278             column->DisplayOrder = column_settings->DisplayOrder;
   3279         else
   3280             column->DisplayOrder = (ImGuiTableColumnIdx)column_n;
   3281         display_order_mask |= (ImU64)1 << column->DisplayOrder;
   3282         column->IsEnabled = column->IsEnabledNextFrame = column_settings->IsEnabled;
   3283         column->SortOrder = column_settings->SortOrder;
   3284         column->SortDirection = column_settings->SortDirection;
   3285     }
   3286 
   3287     // Validate and fix invalid display order data
   3288     const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1;
   3289     if (display_order_mask != expected_display_order_mask)
   3290         for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   3291             table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n;
   3292 
   3293     // Rebuild index
   3294     for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
   3295         table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
   3296 }
   3297 
   3298 static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
   3299 {
   3300     ImGuiContext& g = *ctx;
   3301     for (int i = 0; i != g.Tables.GetSize(); i++)
   3302         g.Tables.GetByIndex(i)->SettingsOffset = -1;
   3303     g.SettingsTables.clear();
   3304 }
   3305 
   3306 // Apply to existing windows (if any)
   3307 static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
   3308 {
   3309     ImGuiContext& g = *ctx;
   3310     for (int i = 0; i != g.Tables.GetSize(); i++)
   3311     {
   3312         ImGuiTable* table = g.Tables.GetByIndex(i);
   3313         table->IsSettingsRequestLoad = true;
   3314         table->SettingsOffset = -1;
   3315     }
   3316 }
   3317 
   3318 static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
   3319 {
   3320     ImGuiID id = 0;
   3321     int columns_count = 0;
   3322     if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2)
   3323         return NULL;
   3324 
   3325     if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id))
   3326     {
   3327         if (settings->ColumnsCountMax >= columns_count)
   3328         {
   3329             TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle
   3330             return settings;
   3331         }
   3332         settings->ID = 0; // Invalidate storage, we won't fit because of a count change
   3333     }
   3334     return ImGui::TableSettingsCreate(id, columns_count);
   3335 }
   3336 
   3337 static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
   3338 {
   3339     // "Column 0  UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v"
   3340     ImGuiTableSettings* settings = (ImGuiTableSettings*)entry;
   3341     float f = 0.0f;
   3342     int column_n = 0, r = 0, n = 0;
   3343 
   3344     if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; }
   3345 
   3346     if (sscanf(line, "Column %d%n", &column_n, &r) == 1)
   3347     {
   3348         if (column_n < 0 || column_n >= settings->ColumnsCount)
   3349             return;
   3350         line = ImStrSkipBlank(line + r);
   3351         char c = 0;
   3352         ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n;
   3353         column->Index = (ImGuiTableColumnIdx)column_n;
   3354         if (sscanf(line, "UserID=0x%08X%n", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; }
   3355         if (sscanf(line, "Width=%d%n", &n, &r) == 1)            { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; }
   3356         if (sscanf(line, "Weight=%f%n", &f, &r) == 1)           { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; }
   3357         if (sscanf(line, "Visible=%d%n", &n, &r) == 1)          { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; }
   3358         if (sscanf(line, "Order=%d%n", &n, &r) == 1)            { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; }
   3359         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; }
   3360     }
   3361 }
   3362 
   3363 static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
   3364 {
   3365     ImGuiContext& g = *ctx;
   3366     for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
   3367     {
   3368         if (settings->ID == 0) // Skip ditched settings
   3369             continue;
   3370 
   3371         // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped
   3372         // (e.g. Order was unchanged)
   3373         const bool save_size    = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0;
   3374         const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0;
   3375         const bool save_order   = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0;
   3376         const bool save_sort    = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0;
   3377         if (!save_size && !save_visible && !save_order && !save_sort)
   3378             continue;
   3379 
   3380         buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve
   3381         buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount);
   3382         if (settings->RefScale != 0.0f)
   3383             buf->appendf("RefScale=%g\n", settings->RefScale);
   3384         ImGuiTableColumnSettings* column = settings->GetColumnSettings();
   3385         for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++)
   3386         {
   3387             // "Column 0  UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v"
   3388             bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1);
   3389             if (!save_column)
   3390                 continue;
   3391             buf->appendf("Column %-2d", column_n);
   3392             if (column->UserID != 0)                    buf->appendf(" UserID=%08X", column->UserID);
   3393             if (save_size && column->IsStretch)         buf->appendf(" Weight=%.4f", column->WidthOrWeight);
   3394             if (save_size && !column->IsStretch)        buf->appendf(" Width=%d", (int)column->WidthOrWeight);
   3395             if (save_visible)                           buf->appendf(" Visible=%d", column->IsEnabled);
   3396             if (save_order)                             buf->appendf(" Order=%d", column->DisplayOrder);
   3397             if (save_sort && column->SortOrder != -1)   buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^');
   3398             buf->append("\n");
   3399         }
   3400         buf->append("\n");
   3401     }
   3402 }
   3403 
   3404 void ImGui::TableSettingsInstallHandler(ImGuiContext* context)
   3405 {
   3406     ImGuiContext& g = *context;
   3407     ImGuiSettingsHandler ini_handler;
   3408     ini_handler.TypeName = "Table";
   3409     ini_handler.TypeHash = ImHashStr("Table");
   3410     ini_handler.ClearAllFn = TableSettingsHandler_ClearAll;
   3411     ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen;
   3412     ini_handler.ReadLineFn = TableSettingsHandler_ReadLine;
   3413     ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll;
   3414     ini_handler.WriteAllFn = TableSettingsHandler_WriteAll;
   3415     g.SettingsHandlers.push_back(ini_handler);
   3416 }
   3417 
   3418 //-------------------------------------------------------------------------
   3419 // [SECTION] Tables: Garbage Collection
   3420 //-------------------------------------------------------------------------
   3421 // - TableRemove() [Internal]
   3422 // - TableGcCompactTransientBuffers() [Internal]
   3423 // - TableGcCompactSettings() [Internal]
   3424 //-------------------------------------------------------------------------
   3425 
   3426 // Remove Table (currently only used by TestEngine)
   3427 void ImGui::TableRemove(ImGuiTable* table)
   3428 {
   3429     //IMGUI_DEBUG_LOG("TableRemove() id=0x%08X\n", table->ID);
   3430     ImGuiContext& g = *GImGui;
   3431     int table_idx = g.Tables.GetIndex(table);
   3432     //memset(table->RawData.Data, 0, table->RawData.size_in_bytes());
   3433     //memset(table, 0, sizeof(ImGuiTable));
   3434     g.Tables.Remove(table->ID, table);
   3435     g.TablesLastTimeActive[table_idx] = -1.0f;
   3436 }
   3437 
   3438 // Free up/compact internal Table buffers for when it gets unused
   3439 void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table)
   3440 {
   3441     //IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID);
   3442     ImGuiContext& g = *GImGui;
   3443     IM_ASSERT(table->MemoryCompacted == false);
   3444     table->SortSpecs.Specs = NULL;
   3445     table->IsSortSpecsDirty = true;
   3446     table->ColumnsNames.clear();
   3447     table->MemoryCompacted = true;
   3448     for (int n = 0; n < table->ColumnsCount; n++)
   3449         table->Columns[n].NameOffset = -1;
   3450     g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f;
   3451 }
   3452 
   3453 void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data)
   3454 {
   3455     temp_data->DrawSplitter.ClearFreeMemory();
   3456     temp_data->SortSpecsMulti.clear();
   3457     temp_data->LastTimeActive = -1.0f;
   3458 }
   3459 
   3460 // Compact and remove unused settings data (currently only used by TestEngine)
   3461 void ImGui::TableGcCompactSettings()
   3462 {
   3463     ImGuiContext& g = *GImGui;
   3464     int required_memory = 0;
   3465     for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
   3466         if (settings->ID != 0)
   3467             required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount);
   3468     if (required_memory == g.SettingsTables.Buf.Size)
   3469         return;
   3470     ImChunkStream<ImGuiTableSettings> new_chunk_stream;
   3471     new_chunk_stream.Buf.reserve(required_memory);
   3472     for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
   3473         if (settings->ID != 0)
   3474             memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount));
   3475     g.SettingsTables.swap(new_chunk_stream);
   3476 }
   3477 
   3478 
   3479 //-------------------------------------------------------------------------
   3480 // [SECTION] Tables: Debugging
   3481 //-------------------------------------------------------------------------
   3482 // - DebugNodeTable() [Internal]
   3483 //-------------------------------------------------------------------------
   3484 
   3485 #ifndef IMGUI_DISABLE_METRICS_WINDOW
   3486 
   3487 static const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_policy)
   3488 {
   3489     sizing_policy &= ImGuiTableFlags_SizingMask_;
   3490     if (sizing_policy == ImGuiTableFlags_SizingFixedFit)    { return "FixedFit"; }
   3491     if (sizing_policy == ImGuiTableFlags_SizingFixedSame)   { return "FixedSame"; }
   3492     if (sizing_policy == ImGuiTableFlags_SizingStretchProp) { return "StretchProp"; }
   3493     if (sizing_policy == ImGuiTableFlags_SizingStretchSame) { return "StretchSame"; }
   3494     return "N/A";
   3495 }
   3496 
   3497 void ImGui::DebugNodeTable(ImGuiTable* table)
   3498 {
   3499     char buf[512];
   3500     char* p = buf;
   3501     const char* buf_end = buf + IM_ARRAYSIZE(buf);
   3502     const bool is_active = (table->LastFrameActive >= ImGui::GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
   3503     ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*");
   3504     if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
   3505     bool open = TreeNode(table, "%s", buf);
   3506     if (!is_active) { PopStyleColor(); }
   3507     if (IsItemHovered())
   3508         GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255));
   3509     if (IsItemVisible() && table->HoveredColumnBody != -1)
   3510         GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255));
   3511     if (!open)
   3512         return;
   3513     bool clear_settings = SmallButton("Clear settings");
   3514     BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f) Sizing: '%s'", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight(), DebugNodeTableGetSizingPolicyDesc(table->Flags));
   3515     BulletText("ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsGivenWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : "");
   3516     BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX);
   3517     BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder);
   3518     BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn);
   3519     //BulletText("BgDrawChannels: %d/%d", 0, table->BgDrawChannelUnfrozen);
   3520     float sum_weights = 0.0f;
   3521     for (int n = 0; n < table->ColumnsCount; n++)
   3522         if (table->Columns[n].Flags & ImGuiTableColumnFlags_WidthStretch)
   3523             sum_weights += table->Columns[n].StretchWeight;
   3524     for (int n = 0; n < table->ColumnsCount; n++)
   3525     {
   3526         ImGuiTableColumn* column = &table->Columns[n];
   3527         const char* name = TableGetColumnName(table, n);
   3528         ImFormatString(buf, IM_ARRAYSIZE(buf),
   3529             "Column %d order %d '%s': offset %+.2f to %+.2f%s\n"
   3530             "Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n"
   3531             "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n"
   3532             "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n"
   3533             "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n"
   3534             "Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s..",
   3535             n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, (n < table->FreezeColumnsRequest) ? " (Frozen)" : "",
   3536             column->IsEnabled, column->IsVisibleX, column->IsVisibleY, column->IsRequestOutput, column->IsSkipItems, column->DrawChannelFrozen, column->DrawChannelUnfrozen,
   3537             column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight, column->StretchWeight > 0.0f ? (column->StretchWeight / sum_weights) * 100.0f : 0.0f,
   3538             column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x,
   3539             column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX,
   3540             column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" : "", column->UserID, column->Flags,
   3541             (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "",
   3542             (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "",
   3543             (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : "");
   3544         Bullet();
   3545         Selectable(buf);
   3546         if (IsItemHovered())
   3547         {
   3548             ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y);
   3549             GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255));
   3550         }
   3551     }
   3552     if (ImGuiTableSettings* settings = TableGetBoundSettings(table))
   3553         DebugNodeTableSettings(settings);
   3554     if (clear_settings)
   3555         table->IsResetAllRequest = true;
   3556     TreePop();
   3557 }
   3558 
   3559 void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings)
   3560 {
   3561     if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount))
   3562         return;
   3563     BulletText("SaveFlags: 0x%08X", settings->SaveFlags);
   3564     BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax);
   3565     for (int n = 0; n < settings->ColumnsCount; n++)
   3566     {
   3567         ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n];
   3568         ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None;
   3569         BulletText("Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X",
   3570             n, column_settings->DisplayOrder, column_settings->SortOrder,
   3571             (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" : (sort_dir == ImGuiSortDirection_Descending) ? "Des" : "---",
   3572             column_settings->IsEnabled, column_settings->IsStretch ? "Weight" : "Width ", column_settings->WidthOrWeight, column_settings->UserID);
   3573     }
   3574     TreePop();
   3575 }
   3576 
   3577 #else // #ifndef IMGUI_DISABLE_METRICS_WINDOW
   3578 
   3579 void ImGui::DebugNodeTable(ImGuiTable*) {}
   3580 void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {}
   3581 
   3582 #endif
   3583 
   3584 
   3585 //-------------------------------------------------------------------------
   3586 // [SECTION] Columns, BeginColumns, EndColumns, etc.
   3587 // (This is a legacy API, prefer using BeginTable/EndTable!)
   3588 //-------------------------------------------------------------------------
   3589 // FIXME: sizing is lossy when columns width is very small (default width may turn negative etc.)
   3590 //-------------------------------------------------------------------------
   3591 // - SetWindowClipRectBeforeSetChannel() [Internal]
   3592 // - GetColumnIndex()
   3593 // - GetColumnsCount()
   3594 // - GetColumnOffset()
   3595 // - GetColumnWidth()
   3596 // - SetColumnOffset()
   3597 // - SetColumnWidth()
   3598 // - PushColumnClipRect() [Internal]
   3599 // - PushColumnsBackground() [Internal]
   3600 // - PopColumnsBackground() [Internal]
   3601 // - FindOrCreateColumns() [Internal]
   3602 // - GetColumnsID() [Internal]
   3603 // - BeginColumns()
   3604 // - NextColumn()
   3605 // - EndColumns()
   3606 // - Columns()
   3607 //-------------------------------------------------------------------------
   3608 
   3609 // [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences,
   3610 // they would meddle many times with the underlying ImDrawCmd.
   3611 // Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let
   3612 // the subsequent single call to SetCurrentChannel() does it things once.
   3613 void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect)
   3614 {
   3615     ImVec4 clip_rect_vec4 = clip_rect.ToVec4();
   3616     window->ClipRect = clip_rect;
   3617     window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4;
   3618     window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4;
   3619 }
   3620 
   3621 int ImGui::GetColumnIndex()
   3622 {
   3623     ImGuiWindow* window = GetCurrentWindowRead();
   3624     return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0;
   3625 }
   3626 
   3627 int ImGui::GetColumnsCount()
   3628 {
   3629     ImGuiWindow* window = GetCurrentWindowRead();
   3630     return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1;
   3631 }
   3632 
   3633 float ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm)
   3634 {
   3635     return offset_norm * (columns->OffMaxX - columns->OffMinX);
   3636 }
   3637 
   3638 float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset)
   3639 {
   3640     return offset / (columns->OffMaxX - columns->OffMinX);
   3641 }
   3642 
   3643 static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f;
   3644 
   3645 static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index)
   3646 {
   3647     // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
   3648     // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
   3649     ImGuiContext& g = *GImGui;
   3650     ImGuiWindow* window = g.CurrentWindow;
   3651     IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.
   3652     IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
   3653 
   3654     float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x;
   3655     x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
   3656     if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths))
   3657         x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
   3658 
   3659     return x;
   3660 }
   3661 
   3662 float ImGui::GetColumnOffset(int column_index)
   3663 {
   3664     ImGuiWindow* window = GetCurrentWindowRead();
   3665     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3666     if (columns == NULL)
   3667         return 0.0f;
   3668 
   3669     if (column_index < 0)
   3670         column_index = columns->Current;
   3671     IM_ASSERT(column_index < columns->Columns.Size);
   3672 
   3673     const float t = columns->Columns[column_index].OffsetNorm;
   3674     const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t);
   3675     return x_offset;
   3676 }
   3677 
   3678 static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false)
   3679 {
   3680     if (column_index < 0)
   3681         column_index = columns->Current;
   3682 
   3683     float offset_norm;
   3684     if (before_resize)
   3685         offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
   3686     else
   3687         offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
   3688     return ImGui::GetColumnOffsetFromNorm(columns, offset_norm);
   3689 }
   3690 
   3691 float ImGui::GetColumnWidth(int column_index)
   3692 {
   3693     ImGuiContext& g = *GImGui;
   3694     ImGuiWindow* window = g.CurrentWindow;
   3695     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3696     if (columns == NULL)
   3697         return GetContentRegionAvail().x;
   3698 
   3699     if (column_index < 0)
   3700         column_index = columns->Current;
   3701     return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
   3702 }
   3703 
   3704 void ImGui::SetColumnOffset(int column_index, float offset)
   3705 {
   3706     ImGuiContext& g = *GImGui;
   3707     ImGuiWindow* window = g.CurrentWindow;
   3708     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3709     IM_ASSERT(columns != NULL);
   3710 
   3711     if (column_index < 0)
   3712         column_index = columns->Current;
   3713     IM_ASSERT(column_index < columns->Columns.Size);
   3714 
   3715     const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1);
   3716     const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
   3717 
   3718     if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow))
   3719         offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
   3720     columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX);
   3721 
   3722     if (preserve_width)
   3723         SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
   3724 }
   3725 
   3726 void ImGui::SetColumnWidth(int column_index, float width)
   3727 {
   3728     ImGuiWindow* window = GetCurrentWindowRead();
   3729     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3730     IM_ASSERT(columns != NULL);
   3731 
   3732     if (column_index < 0)
   3733         column_index = columns->Current;
   3734     SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
   3735 }
   3736 
   3737 void ImGui::PushColumnClipRect(int column_index)
   3738 {
   3739     ImGuiWindow* window = GetCurrentWindowRead();
   3740     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3741     if (column_index < 0)
   3742         column_index = columns->Current;
   3743 
   3744     ImGuiOldColumnData* column = &columns->Columns[column_index];
   3745     PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false);
   3746 }
   3747 
   3748 // Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns)
   3749 void ImGui::PushColumnsBackground()
   3750 {
   3751     ImGuiWindow* window = GetCurrentWindowRead();
   3752     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3753     if (columns->Count == 1)
   3754         return;
   3755 
   3756     // Optimization: avoid SetCurrentChannel() + PushClipRect()
   3757     columns->HostBackupClipRect = window->ClipRect;
   3758     SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect);
   3759     columns->Splitter.SetCurrentChannel(window->DrawList, 0);
   3760 }
   3761 
   3762 void ImGui::PopColumnsBackground()
   3763 {
   3764     ImGuiWindow* window = GetCurrentWindowRead();
   3765     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3766     if (columns->Count == 1)
   3767         return;
   3768 
   3769     // Optimization: avoid PopClipRect() + SetCurrentChannel()
   3770     SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect);
   3771     columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1);
   3772 }
   3773 
   3774 ImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id)
   3775 {
   3776     // We have few columns per window so for now we don't need bother much with turning this into a faster lookup.
   3777     for (int n = 0; n < window->ColumnsStorage.Size; n++)
   3778         if (window->ColumnsStorage[n].ID == id)
   3779             return &window->ColumnsStorage[n];
   3780 
   3781     window->ColumnsStorage.push_back(ImGuiOldColumns());
   3782     ImGuiOldColumns* columns = &window->ColumnsStorage.back();
   3783     columns->ID = id;
   3784     return columns;
   3785 }
   3786 
   3787 ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count)
   3788 {
   3789     ImGuiWindow* window = GetCurrentWindow();
   3790 
   3791     // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
   3792     // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
   3793     PushID(0x11223347 + (str_id ? 0 : columns_count));
   3794     ImGuiID id = window->GetID(str_id ? str_id : "columns");
   3795     PopID();
   3796 
   3797     return id;
   3798 }
   3799 
   3800 void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags)
   3801 {
   3802     ImGuiContext& g = *GImGui;
   3803     ImGuiWindow* window = GetCurrentWindow();
   3804 
   3805     IM_ASSERT(columns_count >= 1);
   3806     IM_ASSERT(window->DC.CurrentColumns == NULL);   // Nested columns are currently not supported
   3807 
   3808     // Acquire storage for the columns set
   3809     ImGuiID id = GetColumnsID(str_id, columns_count);
   3810     ImGuiOldColumns* columns = FindOrCreateColumns(window, id);
   3811     IM_ASSERT(columns->ID == id);
   3812     columns->Current = 0;
   3813     columns->Count = columns_count;
   3814     columns->Flags = flags;
   3815     window->DC.CurrentColumns = columns;
   3816 
   3817     columns->HostCursorPosY = window->DC.CursorPos.y;
   3818     columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x;
   3819     columns->HostInitialClipRect = window->ClipRect;
   3820     columns->HostBackupParentWorkRect = window->ParentWorkRect;
   3821     window->ParentWorkRect = window->WorkRect;
   3822 
   3823     // Set state for first column
   3824     // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect
   3825     const float column_padding = g.Style.ItemSpacing.x;
   3826     const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize));
   3827     const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f);
   3828     const float max_2 = window->WorkRect.Max.x + half_clip_extend_x;
   3829     columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f);
   3830     columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f);
   3831     columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;
   3832 
   3833     // Clear data if columns count changed
   3834     if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
   3835         columns->Columns.resize(0);
   3836 
   3837     // Initialize default widths
   3838     columns->IsFirstFrame = (columns->Columns.Size == 0);
   3839     if (columns->Columns.Size == 0)
   3840     {
   3841         columns->Columns.reserve(columns_count + 1);
   3842         for (int n = 0; n < columns_count + 1; n++)
   3843         {
   3844             ImGuiOldColumnData column;
   3845             column.OffsetNorm = n / (float)columns_count;
   3846             columns->Columns.push_back(column);
   3847         }
   3848     }
   3849 
   3850     for (int n = 0; n < columns_count; n++)
   3851     {
   3852         // Compute clipping rectangle
   3853         ImGuiOldColumnData* column = &columns->Columns[n];
   3854         float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n));
   3855         float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
   3856         column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
   3857         column->ClipRect.ClipWithFull(window->ClipRect);
   3858     }
   3859 
   3860     if (columns->Count > 1)
   3861     {
   3862         columns->Splitter.Split(window->DrawList, 1 + columns->Count);
   3863         columns->Splitter.SetCurrentChannel(window->DrawList, 1);
   3864         PushColumnClipRect(0);
   3865     }
   3866 
   3867     // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user.
   3868     float offset_0 = GetColumnOffset(columns->Current);
   3869     float offset_1 = GetColumnOffset(columns->Current + 1);
   3870     float width = offset_1 - offset_0;
   3871     PushItemWidth(width * 0.65f);
   3872     window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
   3873     window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
   3874     window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;
   3875 }
   3876 
   3877 void ImGui::NextColumn()
   3878 {
   3879     ImGuiWindow* window = GetCurrentWindow();
   3880     if (window->SkipItems || window->DC.CurrentColumns == NULL)
   3881         return;
   3882 
   3883     ImGuiContext& g = *GImGui;
   3884     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3885 
   3886     if (columns->Count == 1)
   3887     {
   3888         window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
   3889         IM_ASSERT(columns->Current == 0);
   3890         return;
   3891     }
   3892 
   3893     // Next column
   3894     if (++columns->Current == columns->Count)
   3895         columns->Current = 0;
   3896 
   3897     PopItemWidth();
   3898 
   3899     // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect()
   3900     // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them),
   3901     ImGuiOldColumnData* column = &columns->Columns[columns->Current];
   3902     SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
   3903     columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1);
   3904 
   3905     const float column_padding = g.Style.ItemSpacing.x;
   3906     columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
   3907     if (columns->Current > 0)
   3908     {
   3909         // Columns 1+ ignore IndentX (by canceling it out)
   3910         // FIXME-COLUMNS: Unnecessary, could be locked?
   3911         window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding;
   3912     }
   3913     else
   3914     {
   3915         // New row/line: column 0 honor IndentX.
   3916         window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
   3917         columns->LineMinY = columns->LineMaxY;
   3918     }
   3919     window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
   3920     window->DC.CursorPos.y = columns->LineMinY;
   3921     window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
   3922     window->DC.CurrLineTextBaseOffset = 0.0f;
   3923 
   3924     // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup.
   3925     float offset_0 = GetColumnOffset(columns->Current);
   3926     float offset_1 = GetColumnOffset(columns->Current + 1);
   3927     float width = offset_1 - offset_0;
   3928     PushItemWidth(width * 0.65f);
   3929     window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;
   3930 }
   3931 
   3932 void ImGui::EndColumns()
   3933 {
   3934     ImGuiContext& g = *GImGui;
   3935     ImGuiWindow* window = GetCurrentWindow();
   3936     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   3937     IM_ASSERT(columns != NULL);
   3938 
   3939     PopItemWidth();
   3940     if (columns->Count > 1)
   3941     {
   3942         PopClipRect();
   3943         columns->Splitter.Merge(window->DrawList);
   3944     }
   3945 
   3946     const ImGuiOldColumnFlags flags = columns->Flags;
   3947     columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
   3948     window->DC.CursorPos.y = columns->LineMaxY;
   3949     if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize))
   3950         window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX;  // Restore cursor max pos, as columns don't grow parent
   3951 
   3952     // Draw columns borders and handle resize
   3953     // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy
   3954     bool is_being_resized = false;
   3955     if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems)
   3956     {
   3957         // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.
   3958         const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y);
   3959         const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y);
   3960         int dragging_column = -1;
   3961         for (int n = 1; n < columns->Count; n++)
   3962         {
   3963             ImGuiOldColumnData* column = &columns->Columns[n];
   3964             float x = window->Pos.x + GetColumnOffset(n);
   3965             const ImGuiID column_id = columns->ID + ImGuiID(n);
   3966             const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH;
   3967             const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2));
   3968             KeepAliveID(column_id);
   3969             if (IsClippedEx(column_hit_rect, column_id, false))
   3970                 continue;
   3971 
   3972             bool hovered = false, held = false;
   3973             if (!(flags & ImGuiOldColumnFlags_NoResize))
   3974             {
   3975                 ButtonBehavior(column_hit_rect, column_id, &hovered, &held);
   3976                 if (hovered || held)
   3977                     g.MouseCursor = ImGuiMouseCursor_ResizeEW;
   3978                 if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize))
   3979                     dragging_column = n;
   3980             }
   3981 
   3982             // Draw column
   3983             const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
   3984             const float xi = IM_FLOOR(x);
   3985             window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col);
   3986         }
   3987 
   3988         // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
   3989         if (dragging_column != -1)
   3990         {
   3991             if (!columns->IsBeingResized)
   3992                 for (int n = 0; n < columns->Count + 1; n++)
   3993                     columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
   3994             columns->IsBeingResized = is_being_resized = true;
   3995             float x = GetDraggedColumnOffset(columns, dragging_column);
   3996             SetColumnOffset(dragging_column, x);
   3997         }
   3998     }
   3999     columns->IsBeingResized = is_being_resized;
   4000 
   4001     window->WorkRect = window->ParentWorkRect;
   4002     window->ParentWorkRect = columns->HostBackupParentWorkRect;
   4003     window->DC.CurrentColumns = NULL;
   4004     window->DC.ColumnsOffset.x = 0.0f;
   4005     window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
   4006 }
   4007 
   4008 void ImGui::Columns(int columns_count, const char* id, bool border)
   4009 {
   4010     ImGuiWindow* window = GetCurrentWindow();
   4011     IM_ASSERT(columns_count >= 1);
   4012 
   4013     ImGuiOldColumnFlags flags = (border ? 0 : ImGuiOldColumnFlags_NoBorder);
   4014     //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior
   4015     ImGuiOldColumns* columns = window->DC.CurrentColumns;
   4016     if (columns != NULL && columns->Count == columns_count && columns->Flags == flags)
   4017         return;
   4018 
   4019     if (columns != NULL)
   4020         EndColumns();
   4021 
   4022     if (columns_count != 1)
   4023         BeginColumns(id, columns_count, flags);
   4024 }
   4025 
   4026 //-------------------------------------------------------------------------
   4027 
   4028 #endif // #ifndef IMGUI_DISABLE