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

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of qt3d/src/3rdparty/imgui/imgui_tables.cpp