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