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