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