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