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

source code of imgui/imgui_tables.cpp