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