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