duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

imgui_tables.cpp (243349B)


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