ArcdpsExtension
 
Loading...
Searching...
No Matches
MainTable.h
Go to the documentation of this file.
1#pragma once
2
3#ifndef IMGUI_DEFINE_MATH_OPERATORS
4#define IMGUI_DEFINE_MATH_OPERATORS
5#endif
6
7#include "MainWindow.h"
8
9#include "../arcdps_structs.h"
10#include "../ExtensionTranslations.h"
11#include "../ImGui_Math.h"
12#include "../Localization.h"
13#include "../Widgets.h"
14
15#include <algorithm>
16#include <bitset>
17#include <functional>
18#include <map>
19#include <string>
20#include <vector>
21
22#include <nlohmann/json.hpp>
23
24// Disable conversion warnings in this file
25#pragma warning(push)
26#pragma warning(disable : 4267)
27#pragma warning(disable : 4244)
28
29namespace ArcdpsExtension {
30 static constexpr int TABLE_DRAW_CHANNEL_BG0 = 0;
31 static constexpr int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1;
32 static constexpr int TABLE_DRAW_CHANNEL_NOCLIP = 2; // When using ImGuiTableFlags_NoClip (this becomes the last visible channel)
33
34 static constexpr float TABLE_BORDER_SIZE = 1.0f; // hardcoded in imgui 1.80 as well
35 static constexpr float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders.
36 static constexpr 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.
37
45 typedef ImS16 TableColumnIdx;
46
56 ImU32 UserId = 0;
57 std::function<std::string()> Name;
58 std::function<void*()> Texture;
59 std::string Category; // The category for this Column in context menu. `0` is top-level. Only one sublevel is currently supported, e.g. `1.1`.
60 bool DefaultVisibility = true;
61 std::function<std::string()> Popup;
62
63 MainTableColumn(ImU32 pUserId, const std::function<std::string()>& pName, const std::function<void*()>& pTexture, std::string pCategory, bool pDefaultVisibility = true)
64 : UserId(pUserId),
65 Name(pName),
66 Texture(pTexture),
67 Category(std::move(pCategory)),
68 DefaultVisibility(pDefaultVisibility),
69 Popup(pName) {}
70
71 template<typename E>
72 requires std::is_enum_v<E> && std::convertible_to<std::underlying_type_t<E>, ImU32>
73 MainTableColumn(E pUserId, const std::function<std::string()>& pName, const std::function<void*()>& pTexture, std::string pCategory, bool pDefaultVisibility = true)
74 : MainTableColumn(std::to_underlying(pUserId), pName, pTexture, pCategory, pDefaultVisibility) {}
75
76 MainTableColumn(ImU32 pUserId, const std::function<std::string()>& pName, const std::function<void*()>& pTexture, std::string pCategory, const std::function<std::string()>& pPopup, bool pDefaultVisibility = true)
77 : UserId(pUserId),
78 Name(pName),
79 Texture(pTexture),
80 Category(std::move(pCategory)),
81 DefaultVisibility(pDefaultVisibility),
82 Popup(pPopup) {}
83
84 template<typename E>
85 requires std::is_enum_v<E> && std::convertible_to<std::underlying_type_t<E>, ImU32>
86 MainTableColumn(E pUserId, const std::function<std::string()>& pName, const std::function<void*()>& pTexture, std::string pCategory, const std::function<std::string()>& pPopup, bool pDefaultVisibility = true)
87 : MainTableColumn(std::to_underlying(pUserId), pName, pTexture, pCategory, pPopup, pDefaultVisibility) {}
88 };
89
92 MainTableFlags_SubWindow = 1 << 0, // Needed if you want to want the table to have max-height. Window is put in a subwindow and will have scrollbars if needed.
93 };
94 typedef int MainTableFlags;
95
96 // Used to define the Max MaxColumnCount.
97 template<size_t T>
98 concept SmallerThanMaxColumnAmount = requires {
99 requires T < std::numeric_limits<TableColumnIdx>::max();
100 };
101
127 template<size_t MaxColumnCount = 64>
129 class MainTable {
130 public:
131 typedef std::bitset<MaxColumnCount> ColumnBitMask;
132
133 MainTable(const std::vector<MainTableColumn>& pColumns, MainWindow* pMainWindow, MainTableFlags pFlags = 0);
134
135 virtual ~MainTable() = default;
136
137 // copy/move
138 MainTable(const MainTable& pOther) = delete;
139 MainTable(MainTable&& pOther) noexcept = delete;
140 MainTable& operator=(const MainTable& pOther) = delete;
141 MainTable& operator=(MainTable&& pOther) noexcept = delete;
142
143 void Draw();
144 void DrawColumnSetupMenu();
145 static size_t GetMaxColumnCount() { return MaxColumnCount; }
146 void RequestSort() { mSortNeeded = true; }
147
148 void SetSpecificColumnSetup(const std::vector<size_t>& pNewColumns);
151 return mSpecificColumnsActive;
152 }
153
155 float WidthOrWeight = 0.0f;
156 ImGuiID UserID = 0;
157 // TableColumnIdx Index = -1;
160 ImU8 SortDirection : 2 = ImGuiSortDirection_None;
161 ImU8 IsEnabled : 1 = 1; // "Visible" in ini file
162 ImU8 IsStretch : 1 = 0;
163
164 std::strong_ordering operator<=>(const TableColumnSettings& pOther) const {
165 return UserID <=> pOther.UserID;
166 }
167
168 std::strong_ordering operator<=>(const ImGuiID& pOther) const {
169 return UserID <=> pOther;
170 }
171
172 bool operator==(const TableColumnSettings& pOther) const {
173 return UserID == pOther.UserID;
174 }
175
176 bool operator==(const ImGuiID& pOther) const {
177 return UserID == pOther;
178 }
179
180 friend void to_json(nlohmann::json& nlohmann_json_j, const TableColumnSettings& nlohmann_json_t) {
181 nlohmann_json_j["WidthOrWeight"] = nlohmann_json_t.WidthOrWeight;
182 nlohmann_json_j["UserID"] = nlohmann_json_t.UserID;
183 nlohmann_json_j["DisplayOrder"] = nlohmann_json_t.DisplayOrder;
184 nlohmann_json_j["SortOrder"] = nlohmann_json_t.SortOrder;
185 nlohmann_json_j["SortDirection"] = nlohmann_json_t.SortDirection;
186 nlohmann_json_j["IsEnabled"] = nlohmann_json_t.IsEnabled;
187 nlohmann_json_j["IsStretch"] = nlohmann_json_t.IsStretch;
188 }
189
190 friend void from_json(const nlohmann::json& nlohmann_json_j, TableColumnSettings& nlohmann_json_t) {
191 nlohmann_json_j.at("WidthOrWeight").get_to(nlohmann_json_t.WidthOrWeight);
192 nlohmann_json_j.at("UserID").get_to(nlohmann_json_t.UserID);
193 nlohmann_json_j.at("DisplayOrder").get_to(nlohmann_json_t.DisplayOrder);
194 nlohmann_json_j.at("SortOrder").get_to(nlohmann_json_t.SortOrder);
195 nlohmann_json_t.SortDirection = nlohmann_json_j.at("SortDirection").get<ImU8>();
196 nlohmann_json_t.IsEnabled = nlohmann_json_j.at("IsEnabled").get<ImU8>();
197 nlohmann_json_t.IsStretch = nlohmann_json_j.at("IsStretch").get<ImU8>();
198 }
199 };
201 ImU32 Version = 0; // Is set to true if the ImGui Ini was migrated.
202 ImGuiTableFlags SaveFlags = 0; // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..)
203 float RefScale = 0; // Reference scale to be able to rescale columns on font/dpi changes.
204 std::vector<TableColumnSettings> Columns;
205
206 NLOHMANN_DEFINE_TYPE_INTRUSIVE(TableSettings, Version, SaveFlags, RefScale, Columns)
207 };
208
209 protected:
210 // Draw Rows here
211 virtual void DrawRows(TableColumnIdx pFirstColumnIndex) = 0;
212
213 /*
214 * This is called, when the table has to be resorted!
215 * - mColumnSortSpecs is guaranteed to be a valid pointer.
216 * - mSortNeeded is already set back to false.
217 */
218 virtual void Sort(const ImGuiTableColumnSortSpecs* mColumnSortSpecs) = 0;
219
220 virtual void DrawStyleSubMenu();
221 virtual void DrawColumnSetupSubMenu();
222
223 virtual Alignment& getAlignment() = 0;
225 virtual bool getShowScrollbar() { return mMainWindow->GetShowScrollbar(); }
226 virtual std::string getTableId() = 0;
227 virtual bool getMaxHeightActive() { return getMaxDisplayed() != 0; }
228 virtual int& getMaxDisplayed() = 0;
229 virtual bool& getShowAlternatingBackground() = 0;
231 virtual bool& getHighlightHoveredRows() = 0;
232 virtual bool& getShowHeaderAsText() = 0;
233
240 virtual bool getCustomColumnsFeatureActive() { return false; }
241 virtual bool& getCustomColumnsActive() { return mCustomColumnsActiveTemp; }
242 // Used to ignore the first N columns, so they are not changed.
243 virtual int getCustomColumnsFirstColumn() { return 0; }
244
250 virtual void MigrateSettings() {}
251
256 virtual const char* getCategoryName(const std::string& pCat) = 0;
257
258 private:
259 MainWindow* mMainWindow;
260 const std::vector<MainTableColumn>& mColumns;
261 int mCurrentRow = 0;
262 std::atomic_bool mSortNeeded = false;
263 MainTableFlags mFlags = 0;
264
265 struct Category {
266 std::vector<size_t> Indices;
267 std::map<size_t, Category> Categories;
268 };
269 Category mCategories;
270
271 void DrawColumnSetupMenuIteratorFunc(Category pCategory, std::string pCatName = "");
272
273 std::atomic_bool mSpecificColumnsUpdate = false;
274 bool mSpecificColumnsActive = false;
275 std::vector<size_t> mSpecificColumnCache;
276
277 bool mCustomColumnsActiveTemp = false; // only here to allow users to have the custom columns feature disabled.
278
279 // Utilities to control ImGui, use these instead of ImGui directly!
280 protected:
284 void NextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row.
285
289 bool NextColumn();
290
296 void EndMaxHeightRow();
297
298 // Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set)
299 // You can sort your data again when 'SpecsChanged == true'. It will be true with sorting specs have changed since
300 // last call, or the first time.
301 // Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()!
302 ImGuiTableSortSpecs* GetSortSpecs();
303
304 // See "COLUMN SIZING POLICIES" comments at the top of this file
305 // If (init_width_or_weight <= 0.0f) it is ignored
306 void SetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id);
307 void ColumnHeader(const char* label, bool show_label, ImTextureID texture, Alignment alignment, const char* popupText);
308
314 void AlignedTextColumn(const char* text);
315 void AlignedTextColumn(const std::string& text);
316 template<typename... Args>
317 void AlignedTextColumn(std::string_view format, Args&&... args);
318
322 bool SpinnerAligned(const char* label, float radius, float thickness, const ImU32& color);
323
325 bool Begin(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width, ImGuiWindowFlags child_window_flags);
326 void End();
327
329 const char* GetColumnName(int column_n);
330 void SetupScrollFreeze(int columns, int rows);
331
332 // Return the cell rectangle based on currently known height.
333 // - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations.
334 // The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it.
335 // - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right
336 // columns report a small offset so their CellBgRect can extend up to the outer border.
337 ImRect GetCellBgRect(int column_n);
338
343
347 bool IsCurrentRowHovered();
348
349 int GetColumnIndex();
350
351 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api.
352 // We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping.
353 // This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped".
354 struct TableColumn {
355 ImGuiTableColumnFlags Flags; // Flags after some patching (not directly same as provided by user). See ImGuiTableColumnFlags_
356 float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space.
357 float MinX; // Absolute positions
358 float MaxX;
359 float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout()
360 float WidthAuto; // Automatic width
361 float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially.
362 float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_).
363 ImRect ClipRect; // Clipping rectangle for the column
364 ImGuiID UserID; // Optional, value passed to TableSetupColumn()
365 float WorkMinX; // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column
366 float WorkMaxX; // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2)
367 float ItemWidth; // Current item width for the column, preserved across rows
368 float ContentMaxXFrozen; // Contents maximum position for frozen rows (apart from headers), from which we can infer content width.
370 float ContentMaxXHeadersUsed; // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls
372 ImS16 NameOffset; // Offset into parent ColumnsNames[]
373 TableColumnIdx DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users)
374 TableColumnIdx IndexWithinEnabledSet; // Index within enabled/visible set (<= IndexToDisplayOrder)
375 TableColumnIdx PrevEnabledColumn; // Index of prev enabled/visible column within Columns[], -1 if first enabled/visible column
376 TableColumnIdx NextEnabledColumn; // Index of next enabled/visible column within Columns[], -1 if last enabled/visible column
377 TableColumnIdx SortOrder; // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort
378 ImGuiTableDrawChannelIdx DrawChannelCurrent; // Index within DrawSplitter.Channels[]
379 ImGuiTableDrawChannelIdx DrawChannelFrozen;
380 ImGuiTableDrawChannelIdx DrawChannelUnfrozen;
381 bool IsEnabled; // Is the column not marked Hidden by the user? (even if off view, e.g. clipped by scrolling).
383 bool IsVisibleX; // Is actually in view (e.g. overlapping the host window clipping rectangle, not scrolled).
385 bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not.
386 bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen).
388 ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte
389 ImU8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit
390 ImU8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem
391 ImU8 SortDirection : 2; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending
392 ImU8 SortDirectionsAvailCount : 2; // Number of available sort directions (0 to 3)
393 ImU8 SortDirectionsAvailMask : 4; // Mask of available sort directions (1-bit each)
394 ImU8 SortDirectionsAvailList; // Ordered of available sort directions (2-bits each)
395
397 memset(this, 0, sizeof(*this));
398 StretchWeight = WidthRequest = -1.0f;
399 NameOffset = -1;
402 SortOrder = -1;
403 SortDirection = ImGuiSortDirection_None;
405 }
406 };
407
408 // FIXME-TABLE: transient data could be stored in a per-stacked table structure: DrawSplitter, SortSpecs, incoming RowData
409 struct Table {
410 ImGuiID ID;
411 ImGuiTableFlags Flags;
412 void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[]
413 ImSpan<TableColumn> Columns; // Point within RawData[]
414 ImSpan<TableColumnIdx> DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1)
415 ImSpan<ImGuiTableCellData> RowCellData; // Point within RawData[]. Store cells background requests for current row.
416 ColumnBitMask EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map
417 ColumnBitMask EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data
418 ColumnBitMask VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect)
419 ColumnBitMask RequestOutputMaskByIndex; // Column Index -> IsVisible || AutoFit (== expect user to submit items)
420 ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order)
421 int SettingsOffset; // Offset in g.SettingsTables
423 int ColumnsCount; // Number of columns declared in BeginTable()
425 int CurrentHoveredRow; // Row that is currently hovered
427 ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched.
428 ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with
429 float RowPosY1;
430 float RowPosY2;
431 float RowMinHeight; // Height submitted to TableNextRow()
434 ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_
435 ImGuiTableRowFlags LastRowFlags : 16;
436 int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper), not same as CurrentRow because header rows typically don't increase this.
437 ImU32 RowBgColor[2]; // Background color override for current row.
440 float BorderX1;
441 float BorderX2;
445 float CellPaddingX; // Padding from each borders
447 float CellSpacingX1; // Spacing between non-bordered cells
449 float LastOuterHeight; // Outer height from last frame
450 float LastFirstRowHeight; // Height of first row from last frame
451 float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details.
452 float ColumnsGivenWidth; // Sum of current column width
453 float ColumnsAutoFitWidth; // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window
455 float ResizeLockMinContentsX2; // Lock minimum contents width while resizing down in order to not create feedback loops. But we allow growing the table.
456 float RefScale; // Reference scale to be able to rescale columns on font/dpi changes.
457 ImRect OuterRect; // Note: for non-scrolling table, OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable().
458 ImRect InnerRect; // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is
459 ImRect WorkRect;
461 ImRect BgClipRect; // We use this to cpu-clip cell background color fill
462 ImRect Bg0ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG0/1 channel. This tends to be == OuterWindow->ClipRect at BeginTable() because output in BG0/BG1 is cpu-clipped
463 ImRect Bg2ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG2 channel. This tends to be a correct, tight-fit, because output to BG2 are done by widgets relying on regular ClipRect.
464 ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window.
465 ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable()
466 ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable()
467 ImRect HostBackupInnerClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground()
468 ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable()
469 ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable()
470 ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable()
471 ImVec2 UserOuterSize; // outer_size.x passed to BeginTable()
472 ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable()
473 float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable()
474 int HostBackupItemWidthStackSize; // Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable()
475 ImGuiWindow* OuterWindow; // Parent window for the table
476 ImGuiWindow* InnerWindow; // Window holding the table data (== OuterWindow or a child window)
477 ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names
478 ImDrawListSplitter DrawSplitter; // We carry our own ImDrawList splitter to allow recursion (FIXME: could be stored outside, worst case we need 1 splitter per recursing table)
479 ImGuiTableColumnSortSpecs SortSpecsSingle;
480 ImVector<ImGuiTableColumnSortSpecs> SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would work be good.
481 ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs()
483 TableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount)
484 TableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns (<= ColumnsCount)
485 TableColumnIdx DeclColumnsCount; // Count calls to TableSetupColumn()
486 TableColumnIdx HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column!
487 TableColumnIdx HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing).
488 TableColumnIdx AutoFitSingleColumn; // Index of single column requesting auto-fit.
489 TableColumnIdx ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0.
490 TableColumnIdx LastResizedColumn; // Index of column being resized from previous frame.
491 TableColumnIdx HeldHeaderColumn; // Index of column header being held.
492 TableColumnIdx ReorderColumn; // Index of column being reordered. (not cleared)
494 TableColumnIdx LeftMostStretchedColumn; // Index of left-most stretched column.
495 TableColumnIdx RightMostStretchedColumn; // Index of right-most stretched column.
496 TableColumnIdx RightMostEnabledColumn; // Index of right-most non-hidden column.
497 TableColumnIdx ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot
498 TableColumnIdx FreezeRowsRequest; // Requested frozen rows count
499 TableColumnIdx FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset)
500 TableColumnIdx FreezeColumnsRequest; // Requested frozen columns count
501 TableColumnIdx FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset)
502 TableColumnIdx RowCellDataCurrent; // Index of current RowCellData[] entry in current row
503 ImGuiTableDrawChannelIdx DummyDrawChannel; // Redirect non-visible columns here.
504 ImGuiTableDrawChannelIdx Bg2DrawChannelCurrent; // For Selectable() and other widgets drawing accross columns after the freezing line. Index within DrawSplitter.Channels[]
505 ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen;
506 bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row.
507 bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow().
510 bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag.
511 bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted).
513 bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data.
514 bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1)
517 bool IsUnfrozenRows; // Set when we got past the frozen row.
518 bool IsDefaultSizingPolicy; // Set if user didn't explicitely set a sizing policy in BeginTable()
520 bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis
521
522 IMGUI_API Table() {
523 memset(this, 0, sizeof(*this));
524 LastFrameActive = -1;
525 }
526 IMGUI_API ~Table() { IM_FREE(RawData); }
527 };
528
530
531 private:
532 static ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window);
533
534 // For reference, the average total _allocation count_ for a table is:
535 // + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables)
536 // + 1 (for table->RawData allocated below)
537 // + 1 (for table->ColumnsNames, if names are used)
538 // + 1 (for table->Splitter._Channels)
539 // + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels)
540 // Where active_channels_count is variable but often == columns_count or columns_count + 1, see TableSetupDrawChannels() for details.
541 // Unused channels don't perform their +2 allocations.
542 void BeginInitMemory(int columns_count);
543
544 // Restore initial state of table (with or without saved settings)
545 void ResetSettings();
546 void LoadSettingsCustom();
547 void SaveSettingsCustom();
548 void MigrateIniSettings();
549
550 // Apply queued resizing/reordering/hiding requests
551 void BeginApplyRequests();
552
553 // Get settings for a given table, NULL if none
554 ImGuiTableSettings* GetBoundSettings();
555
556 // Layout columns for the frame. This is in essence the followup to BeginTable().
557 // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() to be called first.
558 // FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for _WidthAuto columns.
559 // Increase feedback side-effect with widgets relying on WorkRect.Max.x... Maybe provide a default distribution for _WidthAuto columns?
560 void UpdateLayout();
561
562 void SortSpecsBuild();
563
564 // Adjust flags: default width mode + stretch columns are not allowed when auto extending
565 void TableSetupColumnFlags(TableColumn* column, ImGuiTableColumnFlags flags_in);
566
567 void EndRow();
568
569 // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow)
570 void DrawBorders();
571
572 // This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable().
573 // For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect,
574 // actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels().
575 //
576 // Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve
577 // this we merge their clip rect and make them contiguous in the channel list, so they can be merged
578 // by the call to DrawSplitter.Merge() following to the call to this function.
579 // We reorder draw commands by arranging them into a maximum of 4 distinct groups:
580 //
581 // 1 group: 2 groups: 2 groups: 4 groups:
582 // [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze
583 // [ .. ] or no scroll [ 2. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll
584 //
585 // Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled).
586 // When the contents of a column didn't stray off its limit, we move its channels into the corresponding group
587 // based on its position (within frozen rows/columns groups or not).
588 // At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect.
589 // This function assume that each column are pointing to a distinct draw channel,
590 // otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask.
591 //
592 // Column channels will not be merged into one of the 1-4 groups in the following cases:
593 // - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value).
594 // Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds
595 // matches, by e.g. calling SetCursorScreenPos().
596 // - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here..
597 // we could do better but it's going to be rare and probably not worth the hassle.
598 // Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd.
599 //
600 // This function is particularly tricky to understand.. take a breath.
601 void MergeDrawChannels();
602
603 // Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field
604 float GetColumnWidthAuto(TableColumn* column);
605
606 // Adjust flags: default width mode + stretch columns are not allowed when auto extending
607 void SetupColumnFlags(TableColumn* column, ImGuiTableColumnFlags flags_in);
608
609 // Maximum column content width given current layout. Use column->MinX so this value on a per-column basis.
610 float GetMaxColumnWidth(int column_n);
611
612 // Allocate draw channels. Called by TableUpdateLayout()
613 // - We allocate them following storage order instead of display order so reordering columns won't needlessly
614 // increase overall dormant memory cost.
615 // - We isolate headers draw commands in their own channels instead of just altering clip rects.
616 // This is in order to facilitate merging of draw commands.
617 // - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels.
618 // - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other
619 // channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged.
620 // - We allocate 1 or 2 background draw channels. This is because we know TablePushBackgroundChannel() is only used for
621 // horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4).
622 // Draw channel allocation (before merging):
623 // - NoClip --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call)
624 // - Clip --> 2+D+N channels
625 // - FreezeRows --> 2+D+N*2 (unless scrolling value is zero)
626 // - FreezeRows || FreezeColunns --> 3+D+N*2 (unless scrolling value is zero)
627 // Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0.
628 void SetupDrawChannels();
629
630 // Process hit-testing on resizing borders. Actual size change will be applied in EndTable()
631 // - Set table->HoveredColumnBorder with a short delay/timer to reduce feedback noise
632 // - Submit ahead of table contents and header, use ImGuiButtonFlags_AllowItemOverlap to prioritize widgets
633 // overlapping the same area.
634 void UpdateBorders();
635
636 void SortSpecsSanitize();
637
638 // Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending)
639 void FixColumnSortDirection(TableColumn* column);
640
641 static ImGuiSortDirection GetColumnAvailSortDirection(TableColumn* column, int n) {
642 IM_ASSERT(n < column->SortDirectionsAvailCount);
643 return (column->SortDirectionsAvailList >> (n << 1)) & 0x03;
644 }
645
646 void EndCell();
647
648 // Return the resizing ID for the right-side of the given column.
649 ImGuiID GetColumnResizeID(int column_n, int instance_no) {
650 IM_ASSERT(column_n >= 0 && column_n < mTable.ColumnsCount);
651 ImGuiID id = mTable.ID + 1 + (instance_no * mTable.ColumnsCount) + column_n;
652 return id;
653 }
654
655 // Disable clipping then auto-fit, will take 2 frames
656 // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns)
657 void SetColumnWidthAutoSingle(int column_n) {
658 // Single auto width uses auto-fit
659 TableColumn* column = &mTable.Columns[column_n];
660 if (!column->IsEnabled)
661 return;
662 column->CannotSkipItemsQueue = (1 << 0);
664 }
665
666 void BeginCell(int column_n);
667
668 void BeginRow();
669
670 void SetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1);
671
672 // Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert
673 // the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code.
674 void SetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs);
675
676 // Calculate next sort direction that would be set after clicking the column
677 // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click.
678 // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op.
679 IM_STATIC_ASSERT(ImGuiSortDirection_None == 0 && ImGuiSortDirection_Ascending == 1 && ImGuiSortDirection_Descending == 2);
680 ImGuiSortDirection GetColumnNextSortDirection(TableColumn* column);
681
682 // 'width' = inner column width, without padding
683 void SetColumnWidth(int column_n, float width);
684
685 void UpdateColumnsWeightFromWidth();
686 };
687
688 template<size_t MaxColumnCount>
689 requires SmallerThanMaxColumnAmount<MaxColumnCount>
690 MainTable<MaxColumnCount>::MainTable(const std::vector<MainTableColumn>& pColumns, MainWindow* pMainWindow, MainTableFlags pFlags)
691 : mMainWindow(pMainWindow), mColumns(pColumns), mFlags(pFlags) {
692
693 IM_ASSERT(mColumns.size() < MaxColumnCount);
694
695 mMainWindow->RegisterDrawStyleSubMenuHook([this] { DrawStyleSubMenu(); });
696
697 // generate categories
698 for (size_t i = 0; i < mColumns.size(); ++i) {
699 const auto& column = mColumns[i];
700 std::string cat = column.Category;
701 size_t pos = 0;
702 std::vector<size_t> token;
703 while (true) {
704 if ((pos = cat.find('.')) != std::string::npos) {
705 std::string sub = cat.substr(0, pos);
706 size_t dat = 0;
707 std::from_chars(sub.data(), sub.data() + sub.size(), dat);
708 token.push_back(dat);
709 cat.erase(0, pos + 1);
710 } else {
711 size_t dat = 0;
712 std::from_chars(cat.data(), cat.data() + cat.size(), dat);
713 token.push_back(dat);
714 break;
715 }
716 }
717
718 if (token[0] == 0) {
719 mCategories.Indices.emplace_back(i);
720 continue;
721 }
722
723 Category* category = &mCategories;
724 for (size_t tok : token) {
725 category = &category->Categories[tok];
726 }
727
728 category->Indices.emplace_back(i);
729 }
730 }
731
732 template<size_t MaxColumnCount>
735 ImGuiTableFlags tableFlags = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_PadOuterX | ImGuiTableFlags_ScrollY;
736
737 if (getShowAlternatingBackground()) {
738 tableFlags |= ImGuiTableFlags_RowBg;
739 }
740
741 ImVec2 outerSize(0.f, getMaxHeightActive() ? mMainWindow->GetMaxCursorPos() : 0.f);
742
743 if (Begin(getTableId().c_str(), mColumns.size(), tableFlags, outerSize, 0,
744 getShowScrollbar() ? 0 : ImGuiWindowFlags_NoScrollbar)) {
748 TableColumnIdx first = 0;
749 for (int i = 0; i <= mTable.ColumnsCount; ++i) {
750 if (mTable.EnabledMaskByDisplayOrder.test(i)) {
751 first = mTable.DisplayOrderToIndex[i];
752 break;
753 }
754 }
755
756 // Column Setup
757 for (int i = 0; i < mColumns.size(); ++i) {
758 const auto& column = mColumns[i];
759
760 ImGuiTableColumnFlags columnFlags = ImGuiTableColumnFlags_PreferSortDescending;
761 if (first == i) {
762 columnFlags |= ImGuiTableColumnFlags_IndentEnable;
763 }
764 if (!column.DefaultVisibility) {
765 columnFlags |= ImGuiTableColumnFlags_DefaultHide;
766 }
767 SetupColumn(column.Name().c_str(), columnFlags, 0, column.UserId);
768 }
769
770 // freeze header
771 SetupScrollFreeze(0, 1);
772
773 NextRow(ImGuiTableRowFlags_Headers);
774
775 for (const auto& column : mColumns) {
776 if (NextColumn()) {
777 ColumnHeader(column.Name().c_str(), false, column.Texture(), getHeaderAlignment(), column.Popup().c_str());
778 }
779 }
780
784 if (ImGuiTableSortSpecs* sortSpecs = GetSortSpecs()) {
785 if (sortSpecs->SpecsDirty) {
786 mSortNeeded = true;
787 }
788
789 bool expected = true;
790 if (mSortNeeded.compare_exchange_strong(expected, false)) {
791 Sort(sortSpecs->Specs);
792
793 sortSpecs->SpecsDirty = false;
794 }
795 }
796
800 mCurrentRow = 0;
801 DrawRows(first);
802
803 End();
804 }
805 }
806
807 template<size_t MaxColumnCount>
809 void MainTable<MaxColumnCount>::DrawColumnSetupMenuIteratorFunc(Category pCategory, std::string pCatName) {
810 for (const auto& index : pCategory.Indices) {
811 MenuItemColumnVisibility(index);
812 }
813
814 for (const auto& [key, value] : pCategory.Categories) {
815 std::string catName = pCatName;
816 if (!catName.empty()) catName.append(".");
817 catName.append(std::to_string(key));
818 if (ImGui::BeginMenu(getCategoryName(catName))) {
819 DrawColumnSetupMenuIteratorFunc(value, catName);
820
821 ImGui::EndMenu();
822 }
823 }
824 }
825
826 template<size_t MaxColumnCount>
827 requires SmallerThanMaxColumnAmount<MaxColumnCount>
829 if (ImGui::BeginMenu(Localization::STranslate(ET_ColumnSetup).c_str())) {
830 DrawColumnSetupSubMenu();
831
832 ImGui::EndMenu();
833 }
834 }
835
836 template<size_t MaxColumnCount>
839 if (getCustomColumnsFeatureActive()) {
840 if (ImGui::Checkbox(Localization::STranslate(ET_ShowBasedOnMap).c_str(), &getCustomColumnsActive())) {
841 if (getCustomColumnsActive()) {
842 mSpecificColumnsUpdate = true;
843 mSpecificColumnsActive = true;
844 }
845 }
846 }
847
848 DrawColumnSetupMenuIteratorFunc(mCategories);
849 }
850
851 template<size_t MaxColumnCount>
853 void MainTable<MaxColumnCount>::SetSpecificColumnSetup(const std::vector<size_t>& pNewColumns) {
854 mSpecificColumnsUpdate = true;
855 mSpecificColumnsActive = true;
856
857 mSpecificColumnCache = pNewColumns;
858 }
859
860 template<size_t MaxColumnCount>
863 mSpecificColumnsUpdate = true;
864 mSpecificColumnsActive = false;
865 }
866
867 template<size_t MaxColumnCount>
870 ImGui::Separator();
871 ImGui::Checkbox(Localization::STranslate(ET_AlternatingRowBg).c_str(), &getShowAlternatingBackground());
872 ImGui::Checkbox(Localization::STranslate(ET_HighlightHoveredRow).c_str(), &getHighlightHoveredRows());
873 ImGui::Checkbox(Localization::STranslate(ET_SettingsShowHeaderText).c_str(), &getShowHeaderAsText());
874 int& maxDisplayed = getMaxDisplayed();
875 if (ImGui::InputInt(Localization::STranslate(ET_MaxDisplayed).c_str(), &maxDisplayed)) {
876 if (maxDisplayed < 0) {
877 maxDisplayed = 0;
878 }
879 }
882 }
883
884 template<size_t MaxColumnCount>
886 void MainTable<MaxColumnCount>::NextRow(ImGuiTableRowFlags row_flags, float min_row_height) {
887 if (!mTable.IsLayoutLocked)
888 UpdateLayout();
889 if (mTable.IsInsideRow)
890 EndRow();
891
892 mTable.LastRowFlags = mTable.RowFlags;
893 mTable.RowFlags = row_flags;
894 mTable.RowMinHeight = min_row_height;
895 BeginRow();
896
897 // We honor min_row_height requested by user, but cannot guarantee per-row maximum height,
898 // because that would essentially require a unique clipping rectangle per-cell.
899 mTable.RowPosY2 += mTable.CellPaddingY * 2.0f;
900 mTable.RowPosY2 = ImMax(mTable.RowPosY2, mTable.RowPosY1 + min_row_height);
901
902 // Disable output until user calls TableNextColumn()
903 mTable.InnerWindow->SkipItems = true;
904 }
905
906 template<size_t MaxColumnCount>
909 if (mTable.IsInsideRow && mTable.CurrentColumn + 1 < mTable.ColumnsCount) {
910 if (mTable.CurrentColumn != -1)
911 EndCell();
912 BeginCell(mTable.CurrentColumn + 1);
913 } else {
914 NextRow();
915 BeginCell(0);
916 }
917
918 // Return whether the column is visible. User may choose to skip submitting items based on this return value,
919 // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
920 int column_n = mTable.CurrentColumn;
921 return (mTable.RequestOutputMaskByIndex.test(column_n)) != 0;
922 }
923
924 template<size_t MaxColumnCount>
927 if (mCurrentRow < getMaxDisplayed()) {
928 mMainWindow->SetMaxHeightCursorPos();
929 }
930
931 ++mCurrentRow;
932 }
933
934 // Helper
935 template<size_t MaxColumnCount>
937 ImGuiTableFlags MainTable<MaxColumnCount>::TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window) {
938 // Adjust flags: set default sizing policy
939 if ((flags & ImGuiTableFlags_SizingMask_) == 0)
940 flags |= ((flags & ImGuiTableFlags_ScrollX) || (outer_window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) ? ImGuiTableFlags_SizingFixedFit : ImGuiTableFlags_SizingStretchSame;
941
942 // Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame
943 if ((flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)
944 flags |= ImGuiTableFlags_NoKeepColumnsVisible;
945
946 // Adjust flags: enforce borders when resizable
947 if (flags & ImGuiTableFlags_Resizable)
948 flags |= ImGuiTableFlags_BordersInnerV;
949
950 // Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on
951 if (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY))
952 flags &= ~(ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY);
953
954 // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody
955 if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize)
956 flags &= ~ImGuiTableFlags_NoBordersInBody;
957
958 // Adjust flags: disable saved settings if there's nothing to save
959 if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0)
960 flags |= ImGuiTableFlags_NoSavedSettings;
961
962 // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set)
963#ifdef IMGUI_HAS_DOCK
964 ImGuiWindow* window_for_settings = outer_window->RootWindowDockStop;
965#else
966 ImGuiWindow* window_for_settings = outer_window->RootWindow;
967#endif
968 if (window_for_settings->Flags & ImGuiWindowFlags_NoSavedSettings)
969 flags |= ImGuiTableFlags_NoSavedSettings;
970
971 return flags;
972 }
973
974 template<size_t MaxColumnCount>
975 requires SmallerThanMaxColumnAmount<MaxColumnCount>
976 void MainTable<MaxColumnCount>::BeginInitMemory(int columns_count) {
977 // Allocate single buffer for our arrays
978 ImSpanAllocator<3> span_allocator;
979 span_allocator.ReserveBytes(0, columns_count * sizeof(TableColumn));
980 span_allocator.ReserveBytes(1, columns_count * sizeof(TableColumnIdx));
981 span_allocator.ReserveBytes(2, columns_count * sizeof(ImGuiTableCellData));
982 mTable.RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes());
983 memset(mTable.RawData, 0, span_allocator.GetArenaSizeInBytes());
984 span_allocator.SetArenaBasePtr(mTable.RawData);
985 span_allocator.GetSpan(0, &mTable.Columns);
986 span_allocator.GetSpan(1, &mTable.DisplayOrderToIndex);
987 span_allocator.GetSpan(2, &mTable.RowCellData);
988 }
989
990 template<size_t MaxColumnCount>
991 requires SmallerThanMaxColumnAmount<MaxColumnCount>
992 void MainTable<MaxColumnCount>::ResetSettings() {
993 mTable.IsInitializing = mTable.IsSettingsDirty = true;
994 mTable.IsResetAllRequest = false;
995 mTable.IsSettingsRequestLoad = false; // Don't reload from ini
996 mTable.SettingsLoadedFlags = ImGuiTableFlags_None; // Mark as nothing loaded so our initialized data becomes authoritative
997 }
998
999 template<size_t MaxColumnCount>
1000 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1001 void MainTable<MaxColumnCount>::LoadSettingsCustom() {
1002 mTable.IsSettingsRequestLoad = false;
1003 if (mTable.Flags & ImGuiTableFlags_NoSavedSettings)
1004 return;
1005
1006 TableSettings& settings = getTableSettings();
1007
1008 // Migration
1009 if (settings.Version == 0) {
1010 MigrateIniSettings();
1011 settings.Version = 1;
1012 }
1013
1014 MigrateSettings();
1015
1016 mTable.SettingsLoadedFlags = settings.SaveFlags;
1017 mTable.RefScale = settings.RefScale;
1018
1019 // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
1020 ColumnBitMask display_order_mask;
1021 for (int idx = 0; idx < mTable.Columns.size(); ++idx) {
1022 TableColumn* column = &mTable.Columns[idx];
1023
1024 // Preset userID, we need it for loading the settings
1025 column->UserID = mColumns[idx].UserId;
1026
1027 const auto& column_settings = std::find(settings.Columns.begin(), settings.Columns.end(), column->UserID);
1028 if (column_settings == settings.Columns.end()) {
1029 continue;
1030 }
1031
1032 if (settings.SaveFlags & ImGuiTableFlags_Resizable) {
1033 if (column_settings->IsStretch)
1034 column->StretchWeight = column_settings->WidthOrWeight;
1035 else
1036 column->WidthRequest = column_settings->WidthOrWeight;
1037 column->AutoFitQueue = 0x00;
1038 }
1039 if (settings.SaveFlags & ImGuiTableFlags_Reorderable)
1040 column->DisplayOrder = column_settings->DisplayOrder;
1041 else
1042 column->DisplayOrder = (TableColumnIdx) idx;
1043 if (column->DisplayOrder >= 0) display_order_mask.set(column->DisplayOrder);
1044 column->IsEnabled = column->IsEnabledNextFrame = column_settings->IsEnabled;
1045 column->SortOrder = column_settings->SortOrder;
1046 column->SortDirection = column_settings->SortDirection;
1047 }
1048
1049 // Validate and fix invalid display order data
1050 if (display_order_mask.count() != settings.Columns.size())
1051 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++)
1052 mTable.Columns[column_n].DisplayOrder = (TableColumnIdx) column_n;
1053
1054 // Rebuild index
1055 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++)
1056 mTable.DisplayOrderToIndex[mTable.Columns[column_n].DisplayOrder] = (TableColumnIdx) column_n;
1057 }
1058
1059 template<size_t MaxColumnCount>
1060 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1061 void MainTable<MaxColumnCount>::BeginApplyRequests() {
1062 // Handle resizing request
1063 // (We process this at the first TableBegin of the frame)
1064 // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling?
1065 if (mTable.InstanceCurrent == 0) {
1066 if (mTable.ResizedColumn != -1 && mTable.ResizedColumnNextWidth != FLT_MAX)
1067 SetColumnWidth(mTable.ResizedColumn, mTable.ResizedColumnNextWidth);
1068 mTable.LastResizedColumn = mTable.ResizedColumn;
1069 mTable.ResizedColumnNextWidth = FLT_MAX;
1070 mTable.ResizedColumn = -1;
1071
1072 // Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy.
1073 // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings.
1074 if (mTable.AutoFitSingleColumn != -1) {
1075 SetColumnWidth(mTable.AutoFitSingleColumn, mTable.Columns[mTable.AutoFitSingleColumn].WidthAuto);
1076 mTable.AutoFitSingleColumn = -1;
1077 }
1078 }
1079
1080 // Handle reordering request
1081 // Note: we don't clear ReorderColumn after handling the request.
1082 if (mTable.InstanceCurrent == 0) {
1083 if (mTable.HeldHeaderColumn == -1 && mTable.ReorderColumn != -1)
1084 mTable.ReorderColumn = -1;
1085 mTable.HeldHeaderColumn = -1;
1086 if (mTable.ReorderColumn != -1 && mTable.ReorderColumnDir != 0) {
1087 // We need to handle reordering across hidden columns.
1088 // In the configuration below, moving C to the right of E will lead to:
1089 // ... C [D] E ---> ... [D] E C (Column name/index)
1090 // ... 2 3 4 ... 2 3 4 (Display order)
1091 const int reorder_dir = mTable.ReorderColumnDir;
1092 IM_ASSERT(reorder_dir == -1 || reorder_dir == +1);
1093 IM_ASSERT(mTable.Flags & ImGuiTableFlags_Reorderable);
1094 TableColumn* src_column = &mTable.Columns[mTable.ReorderColumn];
1095 TableColumn* dst_column = &mTable.Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn];
1096 IM_UNUSED(dst_column);
1097 const int src_order = src_column->DisplayOrder;
1098 const int dst_order = dst_column->DisplayOrder;
1099 src_column->DisplayOrder = (TableColumnIdx) dst_order;
1100 for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir)
1101 mTable.Columns[mTable.DisplayOrderToIndex[order_n]].DisplayOrder -= (TableColumnIdx) reorder_dir;
1102 IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir);
1103
1104 // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[],
1105 // rebuild the later from the former.
1106 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++)
1107 mTable.DisplayOrderToIndex[mTable.Columns[column_n].DisplayOrder] = (TableColumnIdx) column_n;
1108 mTable.ReorderColumnDir = 0;
1109 mTable.IsSettingsDirty = true;
1110 }
1111 }
1112
1113 // Handle display order reset request
1114 if (mTable.IsResetDisplayOrderRequest) {
1115 for (int n = 0; n < mTable.ColumnsCount; n++)
1116 mTable.DisplayOrderToIndex[n] = mTable.Columns[n].DisplayOrder = (TableColumnIdx) n;
1117 mTable.IsResetDisplayOrderRequest = false;
1118 mTable.IsSettingsDirty = true;
1119 }
1120 }
1121
1122 template<size_t MaxColumnCount>
1123 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1124 ImGuiTableSettings* MainTable<MaxColumnCount>::GetBoundSettings() {
1125 if (mTable.SettingsOffset != -1) {
1126 ImGuiContext& g = *GImGui;
1127 ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(mTable.SettingsOffset);
1128 IM_ASSERT(settings->ID == mTable.ID);
1129 if (settings->ColumnsCountMax >= mTable.ColumnsCount)
1130 return settings; // OK
1131 settings->ID = 0; // Invalidate storage, we won't fit because of a count change
1132 }
1133 return NULL;
1134 }
1135
1136 template<size_t MaxColumnCount>
1137 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1138 void MainTable<MaxColumnCount>::UpdateLayout() {
1139 ImGuiContext& g = *GImGui;
1140 IM_ASSERT(mTable.IsLayoutLocked == false);
1141
1142 const ImGuiTableFlags table_sizing_policy = (mTable.Flags & ImGuiTableFlags_SizingMask_);
1143 mTable.IsDefaultDisplayOrder = true;
1144 mTable.ColumnsEnabledCount = 0;
1145 mTable.EnabledMaskByIndex = 0x00;
1146 mTable.EnabledMaskByDisplayOrder = 0x00;
1147 mTable.MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE
1148
1149 // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns.
1150 // Process columns in their visible orders as we are building the Prev/Next indices.
1151 int count_fixed = 0; // Number of columns that have fixed sizing policies
1152 int count_stretch = 0; // Number of columns that have stretch sizing policies
1153 int last_visible_column_idx = -1;
1154 bool has_auto_fit_request = false;
1155 bool has_resizable = false;
1156 float stretch_sum_width_auto = 0.0f;
1157 float fixed_max_width_auto = 0.0f;
1158 for (int order_n = 0; order_n < mTable.ColumnsCount; order_n++) {
1159 const int column_n = mTable.DisplayOrderToIndex[order_n];
1160 if (column_n != order_n)
1161 mTable.IsDefaultDisplayOrder = false;
1162 TableColumn* column = &mTable.Columns[column_n];
1163
1164 // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame.
1165 // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway.
1166 // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect.
1167 if (mTable.DeclColumnsCount <= column_n) {
1168 SetupColumnFlags(column, ImGuiTableColumnFlags_None);
1169 column->NameOffset = -1;
1170 column->UserID = 0;
1171 column->InitStretchWeightOrWidth = -1.0f;
1172 }
1173
1174 // Update Enabled state, mark settings/sortspecs dirty
1175 if (!(mTable.Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide))
1176 column->IsEnabledNextFrame = true;
1177 if (column->IsEnabled != column->IsEnabledNextFrame) {
1178 column->IsEnabled = column->IsEnabledNextFrame;
1179 mTable.IsSettingsDirty = true;
1180 if (!column->IsEnabled && column->SortOrder != -1)
1181 mTable.IsSortSpecsDirty = true;
1182 }
1183 if (column->SortOrder > 0 && !(mTable.Flags & ImGuiTableFlags_SortMulti))
1184 mTable.IsSortSpecsDirty = true;
1185
1186 // Auto-fit unsized columns
1187 const bool start_auto_fit = (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f);
1188 if (start_auto_fit)
1189 column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames
1190
1191 if (!column->IsEnabled) {
1192 column->IndexWithinEnabledSet = -1;
1193 continue;
1194 }
1195
1196 // Mark as enabled and link to previous/next enabled column
1197 column->PrevEnabledColumn = (TableColumnIdx) last_visible_column_idx;
1198 column->NextEnabledColumn = -1;
1199 if (last_visible_column_idx != -1)
1200 mTable.Columns[last_visible_column_idx].NextEnabledColumn = (TableColumnIdx) column_n;
1201 column->IndexWithinEnabledSet = mTable.ColumnsEnabledCount++;
1202 mTable.EnabledMaskByIndex.set(column_n);
1203 mTable.EnabledMaskByDisplayOrder.set(column->DisplayOrder);
1204 last_visible_column_idx = column_n;
1205 IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder);
1206
1207 // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping)
1208 // Combine width from regular rows + width from headers unless requested not to.
1209 if (!column->IsPreserveWidthAuto)
1210 column->WidthAuto = GetColumnWidthAuto(column);
1211
1212 // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto)
1213 const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0;
1214 if (column_is_resizable)
1215 has_resizable = true;
1216 if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f && !column_is_resizable)
1217 column->WidthAuto = column->InitStretchWeightOrWidth;
1218
1219 if (column->AutoFitQueue != 0x00)
1220 has_auto_fit_request = true;
1221 if (column->Flags & ImGuiTableColumnFlags_WidthStretch) {
1222 stretch_sum_width_auto += column->WidthAuto;
1223 count_stretch++;
1224 } else {
1225 fixed_max_width_auto = ImMax(fixed_max_width_auto, column->WidthAuto);
1226 count_fixed++;
1227 }
1228 }
1229 if ((mTable.Flags & ImGuiTableFlags_Sortable) && mTable.SortSpecsCount == 0 && !(mTable.Flags & ImGuiTableFlags_SortTristate))
1230 mTable.IsSortSpecsDirty = true;
1231 mTable.RightMostEnabledColumn = (TableColumnIdx) last_visible_column_idx;
1232 IM_ASSERT(mTable.RightMostEnabledColumn >= 0);
1233
1234 // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible
1235 // to avoid the column fitting having to wait until the first visible frame of the child container (may or not be a good thing).
1236 // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time.
1237 if (has_auto_fit_request && mTable.OuterWindow != mTable.InnerWindow)
1238 mTable.InnerWindow->SkipItems = false;
1239 if (has_auto_fit_request)
1240 mTable.IsSettingsDirty = true;
1241
1242 // [Part 3] Fix column flags and record a few extra information.
1243 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.
1244 float stretch_sum_weights = 0.0f; // Sum of all weights for stretch columns.
1246 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
1247 if (!mTable.EnabledMaskByIndex.test(column_n))
1248 continue;
1249 TableColumn* column = &mTable.Columns[column_n];
1250
1251 const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0;
1252 if (column->Flags & ImGuiTableColumnFlags_WidthFixed) {
1253 // Apply same widths policy
1254 float width_auto = column->WidthAuto;
1255 if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable))
1256 width_auto = fixed_max_width_auto;
1257
1258 // Apply automatic width
1259 // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!)
1260 if (column->AutoFitQueue != 0x00)
1261 column->WidthRequest = width_auto;
1262 else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && mTable.RequestOutputMaskByIndex.test(column_n))
1263 column->WidthRequest = width_auto;
1264
1265 // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets
1266 // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very
1267 // large height (= first frame scrollbar display very off + clipper would skip lots of items).
1268 // This is merely making the side-effect less extreme, but doesn't properly fixes it.
1269 // FIXME: Move this to ->WidthGiven to avoid temporary lossyless?
1270 // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller.
1271 if (column->AutoFitQueue > 0x01 && mTable.IsInitializing && !column->IsPreserveWidthAuto)
1272 column->WidthRequest = ImMax(column->WidthRequest, mTable.MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale?
1273 sum_width_requests += column->WidthRequest;
1274 } else {
1275 // Initialize stretch weight
1276 if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f || !column_is_resizable) {
1277 if (column->InitStretchWeightOrWidth > 0.0f)
1278 column->StretchWeight = column->InitStretchWeightOrWidth;
1279 else if (table_sizing_policy == ImGuiTableFlags_SizingStretchProp)
1280 column->StretchWeight = (column->WidthAuto / stretch_sum_width_auto) * count_stretch;
1281 else
1282 column->StretchWeight = 1.0f;
1283 }
1284
1285 stretch_sum_weights += column->StretchWeight;
1286 if (mTable.LeftMostStretchedColumn == -1 || mTable.Columns[mTable.LeftMostStretchedColumn].DisplayOrder > column->DisplayOrder)
1287 mTable.LeftMostStretchedColumn = (TableColumnIdx) column_n;
1288 if (mTable.RightMostStretchedColumn == -1 || mTable.Columns[mTable.RightMostStretchedColumn].DisplayOrder < column->DisplayOrder)
1289 mTable.RightMostStretchedColumn = (TableColumnIdx) column_n;
1290 }
1291 column->IsPreserveWidthAuto = false;
1292 sum_width_requests += mTable.CellPaddingX * 2.0f;
1293 }
1294 mTable.ColumnsEnabledFixedCount = (TableColumnIdx) count_fixed;
1295
1296 // [Part 4] Apply final widths based on requested widths
1297 const ImRect work_rect = mTable.WorkRect;
1298 const float width_spacings = (mTable.OuterPaddingX * 2.0f) + (mTable.CellSpacingX1 + mTable.CellSpacingX2) * (mTable.ColumnsEnabledCount - 1);
1299 const float width_avail = ((mTable.Flags & ImGuiTableFlags_ScrollX) && mTable.InnerWidth == 0.0f) ? mTable.InnerClipRect.GetWidth() : work_rect.GetWidth();
1300 const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests;
1301 float width_remaining_for_stretched_columns = width_avail_for_stretched_columns;
1302 mTable.ColumnsGivenWidth = width_spacings + (mTable.CellPaddingX * 2.0f) * mTable.ColumnsEnabledCount;
1303 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
1304 if (!mTable.EnabledMaskByIndex.test(column_n))
1305 continue;
1306 TableColumn* column = &mTable.Columns[column_n];
1307
1308 // Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest)
1309 if (column->Flags & ImGuiTableColumnFlags_WidthStretch) {
1310 float weight_ratio = column->StretchWeight / stretch_sum_weights;
1311 column->WidthRequest = IM_FLOOR(ImMax(width_avail_for_stretched_columns * weight_ratio, mTable.MinColumnWidth) + 0.01f);
1312 width_remaining_for_stretched_columns -= column->WidthRequest;
1313 }
1314
1315 // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column
1316 // See additional comments in TableSetColumnWidth().
1317 if (column->NextEnabledColumn == -1 && mTable.LeftMostStretchedColumn != -1)
1318 column->Flags |= ImGuiTableColumnFlags_NoDirectResize_;
1319
1320 // Assign final width, record width in case we will need to shrink
1321 column->WidthGiven = ImFloor(ImMax(column->WidthRequest, mTable.MinColumnWidth));
1322 mTable.ColumnsGivenWidth += column->WidthGiven;
1323 }
1324
1325 // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column).
1326 // Using right-to-left distribution (more likely to match resizing cursor).
1327 if (width_remaining_for_stretched_columns >= 1.0f && !(mTable.Flags & ImGuiTableFlags_PreciseWidths))
1328 for (int order_n = mTable.ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) {
1329 if (!mTable.EnabledMaskByDisplayOrder.test(order_n))
1330 continue;
1331 TableColumn* column = &mTable.Columns[mTable.DisplayOrderToIndex[order_n]];
1332 if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch))
1333 continue;
1334 column->WidthRequest += 1.0f;
1335 column->WidthGiven += 1.0f;
1336 width_remaining_for_stretched_columns -= 1.0f;
1337 }
1338
1339 mTable.HoveredColumnBody = -1;
1340 mTable.HoveredColumnBorder = -1;
1341 const ImRect mouse_hit_rect(mTable.OuterRect.Min.x, mTable.OuterRect.Min.y, mTable.OuterRect.Max.x, ImMax(mTable.OuterRect.Max.y, mTable.OuterRect.Min.y + mTable.LastOuterHeight));
1342 const bool is_hovering_table = ImGui::ItemHoverable(mouse_hit_rect, 0);
1343
1344 // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column
1345 // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping.
1346 int visible_n = 0;
1347 bool offset_x_frozen = (mTable.FreezeColumnsCount > 0);
1348 float offset_x = ((mTable.FreezeColumnsCount > 0) ? mTable.OuterRect.Min.x : work_rect.Min.x) + mTable.OuterPaddingX - mTable.CellSpacingX1;
1349 ImRect host_clip_rect = mTable.InnerClipRect;
1350 //host_clip_rect.Max.x += mTable.CellPaddingX + mTable.CellSpacingX2;
1351 mTable.VisibleMaskByIndex = 0x00;
1352 mTable.RequestOutputMaskByIndex = 0x00;
1353 for (int order_n = 0; order_n < mTable.ColumnsCount; order_n++) {
1354 const int column_n = mTable.DisplayOrderToIndex[order_n];
1355 TableColumn* column = &mTable.Columns[column_n];
1356
1357 column->NavLayerCurrent = (ImS8) ((mTable.FreezeRowsCount > 0 || column_n < mTable.FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
1358
1359 if (offset_x_frozen && mTable.FreezeColumnsCount == visible_n) {
1360 offset_x += work_rect.Min.x - mTable.OuterRect.Min.x;
1361 offset_x_frozen = false;
1362 }
1363
1364 // Clear status flags
1365 column->Flags &= ~ImGuiTableColumnFlags_StatusMask_;
1366
1367 if (!mTable.EnabledMaskByDisplayOrder.test(order_n)) {
1368 // Hidden column: clear a few fields and we are done with it for the remainder of the function.
1369 // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper.
1370 column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x;
1371 column->WidthGiven = 0.0f;
1372 column->ClipRect.Min.y = work_rect.Min.y;
1373 column->ClipRect.Max.y = FLT_MAX;
1374 column->ClipRect.ClipWithFull(host_clip_rect);
1375 column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false;
1376 column->IsSkipItems = true;
1377 column->ItemWidth = 1.0f;
1378 continue;
1379 }
1380
1381 // Detect hovered column
1382 if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x)
1383 mTable.HoveredColumnBody = (TableColumnIdx) column_n;
1384
1385 // Lock start position
1386 column->MinX = offset_x;
1387
1388 // Lock width based on start position and minimum/maximum width for this position
1389 float max_width = GetMaxColumnWidth(column_n);
1390 column->WidthGiven = ImMin(column->WidthGiven, max_width);
1391 column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, mTable.MinColumnWidth));
1392 column->MaxX = offset_x + column->WidthGiven + mTable.CellSpacingX1 + mTable.CellSpacingX2 + mTable.CellPaddingX * 2.0f;
1393
1394 // Lock other positions
1395 // - 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.
1396 // - 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.
1397 // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow.
1398 // - 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.
1399 column->WorkMinX = column->MinX + mTable.CellPaddingX + mTable.CellSpacingX1;
1400 column->WorkMaxX = column->MaxX - mTable.CellPaddingX - mTable.CellSpacingX2; // Expected max
1401 column->ItemWidth = ImFloor(column->WidthGiven * 0.65f);
1402 column->ClipRect.Min.x = column->MinX;
1403 column->ClipRect.Min.y = work_rect.Min.y;
1404 column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX;
1405 column->ClipRect.Max.y = FLT_MAX;
1406 column->ClipRect.ClipWithFull(host_clip_rect);
1407
1408 // Mark column as Clipped (not in sight)
1409 // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables.
1410 // 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.
1411 // Taking advantage of LastOuterHeight would yield good results there...
1412 // 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,
1413 // and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, but could be something else).
1414 // 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.
1415 column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x);
1416 column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y);
1417 const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY;
1418 if (is_visible)
1419 mTable.VisibleMaskByIndex.set(column_n);
1420
1421 // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output.
1422 column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0;
1423 if (column->IsRequestOutput)
1424 mTable.RequestOutputMaskByIndex.set(column_n);
1425
1426 // Mark column as SkipItems (ignoring all items/layout)
1427 column->IsSkipItems = !column->IsEnabled || mTable.HostSkipItems;
1428 if (column->IsSkipItems)
1429 IM_ASSERT(!is_visible);
1430
1431 // Update status flags
1432 column->Flags |= ImGuiTableColumnFlags_IsEnabled;
1433 if (is_visible)
1434 column->Flags |= ImGuiTableColumnFlags_IsVisible;
1435 if (column->SortOrder != -1)
1436 column->Flags |= ImGuiTableColumnFlags_IsSorted;
1437 if (mTable.HoveredColumnBody == column_n)
1438 column->Flags |= ImGuiTableColumnFlags_IsHovered;
1439
1440 // Alignment
1441 // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in
1442 // many cases (to be able to honor this we might be able to store a log of cells width, per row, for
1443 // visible rows, but nav/programmatic scroll would have visible artifacts.)
1444 //if (column->Flags & ImGuiTableColumnFlags_AlignRight)
1445 // column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen);
1446 //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter)
1447 // column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f);
1448
1449 // Reset content width variables
1450 column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX;
1451 column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX;
1452
1453 // Don't decrement auto-fit counters until container window got a chance to submit its items
1454 if (mTable.HostSkipItems == false) {
1455 column->AutoFitQueue >>= 1;
1456 column->CannotSkipItemsQueue >>= 1;
1457 }
1458
1459 if (visible_n < mTable.FreezeColumnsCount)
1460 host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x);
1461
1462 offset_x += column->WidthGiven + mTable.CellSpacingX1 + mTable.CellSpacingX2 + mTable.CellPaddingX * 2.0f;
1463 visible_n++;
1464 }
1465
1466 // [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)
1467 // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either
1468 // because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu.
1469 const float unused_x1 = ImMax(mTable.WorkRect.Min.x, mTable.Columns[mTable.RightMostEnabledColumn].ClipRect.Max.x);
1470 if (is_hovering_table && mTable.HoveredColumnBody == -1) {
1471 if (g.IO.MousePos.x >= unused_x1)
1473 }
1474 if (has_resizable == false && (mTable.Flags & ImGuiTableFlags_Resizable))
1475 mTable.Flags &= ~ImGuiTableFlags_Resizable;
1476
1477 // [Part 8] Lock actual OuterRect/WorkRect right-most position.
1478 // This is done late to handle the case of fixed-columns tables not claiming more widths that they need.
1479 // Because of this we are careful with uses of WorkRect and InnerClipRect before this point.
1480 if (mTable.RightMostStretchedColumn != -1)
1481 mTable.Flags &= ~ImGuiTableFlags_NoHostExtendX;
1482 if (mTable.Flags & ImGuiTableFlags_NoHostExtendX) {
1483 mTable.OuterRect.Max.x = mTable.WorkRect.Max.x = unused_x1;
1484 mTable.InnerClipRect.Max.x = ImMin(mTable.InnerClipRect.Max.x, unused_x1);
1485 }
1486 mTable.InnerWindow->ParentWorkRect = mTable.WorkRect;
1487 mTable.BorderX1 = mTable.InnerClipRect.Min.x; // +((mTable.Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f);
1488 mTable.BorderX2 = mTable.InnerClipRect.Max.x; // +((mTable.Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f);
1489
1490 // [Part 9] Allocate draw channels and setup background cliprect
1491 SetupDrawChannels();
1492
1493 // [Part 10] Hit testing on borders
1494 if (mTable.Flags & ImGuiTableFlags_Resizable)
1495 UpdateBorders();
1496 mTable.LastFirstRowHeight = 0.0f;
1497 mTable.IsLayoutLocked = true;
1498 mTable.IsUsingHeaders = false;
1499
1500 // [Part 11] Context menu
1501 // FIXME: Context menu disabled -knox
1502 // if (mTable.IsContextPopupOpen && mTable.InstanceCurrent == mTable.InstanceInteracted)
1503 // {
1504 // const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, mTable.ID);
1505 // if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings))
1506 // {
1507 // TableDrawContextMenu(table);
1508 // EndPopup();
1509 // }
1510 // else
1511 // {
1512 // mTable.IsContextPopupOpen = false;
1513 // }
1514 // }
1515
1516 // [Part 13] Sanitize and build sort specs before we have a change to use them for display.
1517 // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change)
1518 if (mTable.IsSortSpecsDirty && (mTable.Flags & ImGuiTableFlags_Sortable))
1519 SortSpecsBuild();
1520
1521 // Initial state
1522 ImGuiWindow* inner_window = mTable.InnerWindow;
1523 if (mTable.Flags & ImGuiTableFlags_NoClip)
1524 mTable.DrawSplitter.SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);
1525 else
1526 inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false);
1527 }
1528
1529 template<size_t MaxColumnCount>
1530 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1531 void MainTable<MaxColumnCount>::SortSpecsBuild() {
1532 IM_ASSERT(mTable.IsSortSpecsDirty);
1533 SortSpecsSanitize();
1534
1535 // Write output
1536 mTable.SortSpecsMulti.resize(mTable.SortSpecsCount <= 1 ? 0 : mTable.SortSpecsCount);
1537 ImGuiTableColumnSortSpecs* sort_specs = (mTable.SortSpecsCount == 0) ? NULL : (mTable.SortSpecsCount == 1) ? &mTable.SortSpecsSingle
1538 : mTable.SortSpecsMulti.Data;
1539 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
1540 TableColumn* column = &mTable.Columns[column_n];
1541 if (column->SortOrder == -1)
1542 continue;
1543 IM_ASSERT(column->SortOrder < mTable.SortSpecsCount);
1544 ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder];
1545 sort_spec->ColumnUserID = column->UserID;
1546 sort_spec->ColumnIndex = (TableColumnIdx) column_n;
1547 sort_spec->SortOrder = (TableColumnIdx) column->SortOrder;
1548 sort_spec->SortDirection = column->SortDirection;
1549 }
1550 mTable.SortSpecs.Specs = sort_specs;
1551 mTable.SortSpecs.SpecsCount = mTable.SortSpecsCount;
1552 mTable.SortSpecs.SpecsDirty = true; // Mark as dirty for user
1553 mTable.IsSortSpecsDirty = false; // Mark as not dirty for us
1554 }
1555
1556 template<size_t MaxColumnCount>
1557 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1558 void MainTable<MaxColumnCount>::TableSetupColumnFlags(TableColumn* column, ImGuiTableColumnFlags flags_in) {
1559 ImGuiTableColumnFlags flags = flags_in;
1560
1561 // Sizing Policy
1562 if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) {
1563 const ImGuiTableFlags table_sizing_policy = (mTable.Flags & ImGuiTableFlags_SizingMask_);
1564 if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame)
1565 flags |= ImGuiTableColumnFlags_WidthFixed;
1566 else
1567 flags |= ImGuiTableColumnFlags_WidthStretch;
1568 } else {
1569 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used.
1570 }
1571
1572 // Resize
1573 if ((mTable.Flags & ImGuiTableFlags_Resizable) == 0)
1574 flags |= ImGuiTableColumnFlags_NoResize;
1575
1576 // Sorting
1577 if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending))
1578 flags |= ImGuiTableColumnFlags_NoSort;
1579
1580 // Indentation
1581 if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0)
1582 flags |= (mTable.Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable;
1583
1584 // Alignment
1585 //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0)
1586 // flags |= ImGuiTableColumnFlags_AlignCenter;
1587 //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used.
1588
1589 // Preserve status flags
1590 column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_);
1591
1592 // Build an ordered list of available sort directions
1593 column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0;
1594 if (mTable.Flags & ImGuiTableFlags_Sortable) {
1595 int count = 0, mask = 0, list = 0;
1596 if ((flags & ImGuiTableColumnFlags_PreferSortAscending) != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) {
1597 mask |= 1 << ImGuiSortDirection_Ascending;
1598 list |= ImGuiSortDirection_Ascending << (count << 1);
1599 count++;
1600 }
1601 if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) {
1602 mask |= 1 << ImGuiSortDirection_Descending;
1603 list |= ImGuiSortDirection_Descending << (count << 1);
1604 count++;
1605 }
1606 if ((flags & ImGuiTableColumnFlags_PreferSortAscending) == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) {
1607 mask |= 1 << ImGuiSortDirection_Ascending;
1608 list |= ImGuiSortDirection_Ascending << (count << 1);
1609 count++;
1610 }
1611 if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) {
1612 mask |= 1 << ImGuiSortDirection_Descending;
1613 list |= ImGuiSortDirection_Descending << (count << 1);
1614 count++;
1615 }
1616 if ((mTable.Flags & ImGuiTableFlags_SortTristate) || count == 0) {
1617 mask |= 1 << ImGuiSortDirection_None;
1618 count++;
1619 }
1620 column->SortDirectionsAvailList = (ImU8) list;
1621 column->SortDirectionsAvailMask = (ImU8) mask;
1622 column->SortDirectionsAvailCount = (ImU8) count;
1623 FixColumnSortDirection(column);
1624 }
1625 }
1626
1627 template<size_t MaxColumnCount>
1628 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1629 void MainTable<MaxColumnCount>::EndRow() {
1630 ImGuiContext& g = *GImGui;
1631 ImGuiWindow* window = g.CurrentWindow;
1632 IM_ASSERT(window == mTable.InnerWindow);
1633 IM_ASSERT(mTable.IsInsideRow);
1634
1635 if (mTable.CurrentColumn != -1)
1636 EndCell();
1637
1638 // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is
1639 // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding.
1640 window->DC.CursorPos.y = mTable.RowPosY2;
1641
1642 // Row background fill
1643 const float bg_y1 = mTable.RowPosY1;
1644 const float bg_y2 = mTable.RowPosY2;
1645 const bool unfreeze_rows_actual = (mTable.CurrentRow + 1 == mTable.FreezeRowsCount);
1646 const bool unfreeze_rows_request = (mTable.CurrentRow + 1 == mTable.FreezeRowsRequest);
1647 if (mTable.CurrentRow == 0)
1648 mTable.LastFirstRowHeight = bg_y2 - bg_y1;
1649
1650 const bool is_visible = (bg_y2 >= mTable.InnerClipRect.Min.y && bg_y1 <= mTable.InnerClipRect.Max.y);
1651 if (is_visible) {
1652 // Decide of background color for the row
1653 ImU32 bg_col0 = 0;
1654 ImU32 bg_col1 = 0;
1655 if (mTable.RowBgColor[0] != IM_COL32_DISABLE)
1656 bg_col0 = mTable.RowBgColor[0];
1657 else if (mTable.Flags & ImGuiTableFlags_RowBg)
1658 bg_col0 = ImGui::GetColorU32((mTable.RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg);
1659 if (mTable.RowBgColor[1] != IM_COL32_DISABLE)
1660 bg_col1 = mTable.RowBgColor[1];
1661
1662 // KNOX: override bg color for hovered rows
1663 ImRect row_rect(mTable.WorkRect.Min.x, bg_y1, mTable.WorkRect.Max.x, bg_y2);
1664 row_rect.ClipWith(mTable.BgClipRect);
1665
1666 // [[ DEBUG ]]
1667 // ImGui::GetCurrentWindow()->DrawList->AddRect(row_rect.Min, row_rect.Max, 0xff0000ff);
1668
1669 if (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(row_rect.Min, row_rect.Max, false) && (mTable.CurrentRow > 0 && mTable.IsUsingHeaders)) {
1670 mTable.CurrentHoveredRow = mTable.CurrentRow;
1671
1672 if (getHighlightHoveredRows()) {
1673 bg_col1 = ImGui::GetColorU32(ImGuiCol_FrameBgHovered);
1674 }
1675 } else if (mTable.CurrentHoveredRow == mTable.CurrentRow) {
1676 mTable.CurrentHoveredRow = -1;
1677 }
1678
1679
1680 // Decide of top border color
1681 ImU32 border_col = 0;
1682 const float border_size = TABLE_BORDER_SIZE;
1683 if (mTable.CurrentRow > 0 || mTable.InnerWindow == mTable.OuterWindow)
1684 if (mTable.Flags & ImGuiTableFlags_BordersInnerH)
1685 border_col = (mTable.LastRowFlags & ImGuiTableRowFlags_Headers) ? mTable.BorderColorStrong : mTable.BorderColorLight;
1686
1687 const bool draw_cell_bg_color = mTable.RowCellDataCurrent >= 0;
1688 const bool draw_strong_bottom_border = unfreeze_rows_actual;
1689 if ((bg_col0 | bg_col1 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) {
1690 // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is
1691 // always followed by a change of clipping rectangle we perform the smallest overwrite possible here.
1692 if ((mTable.Flags & ImGuiTableFlags_NoClip) == 0)
1693 window->DrawList->_CmdHeader.ClipRect = mTable.Bg0ClipRectForDrawCmd.ToVec4();
1694 mTable.DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0);
1695 }
1696
1697 // Draw row background
1698 // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle
1699 if (bg_col0 || bg_col1) {
1700 if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y)
1701 window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0);
1702 if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y)
1703 window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1);
1704 }
1705
1706 // Draw cell background color
1707 if (draw_cell_bg_color) {
1708 ImGuiTableCellData* cell_data_end = &mTable.RowCellData[mTable.RowCellDataCurrent];
1709 for (ImGuiTableCellData* cell_data = &mTable.RowCellData[0]; cell_data <= cell_data_end; cell_data++) {
1710 const TableColumn* column = &mTable.Columns[cell_data->Column];
1711 ImRect cell_bg_rect = GetCellBgRect(cell_data->Column);
1712 cell_bg_rect.ClipWith(mTable.BgClipRect);
1713 cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x); // So that first column after frozen one gets clipped
1714 cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX);
1715 window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor);
1716 }
1717 }
1718
1719 // Draw top border
1720 if (border_col && bg_y1 >= mTable.BgClipRect.Min.y && bg_y1 < mTable.BgClipRect.Max.y)
1721 window->DrawList->AddLine(ImVec2(mTable.BorderX1, bg_y1), ImVec2(mTable.BorderX2, bg_y1), border_col, border_size);
1722
1723 // Draw bottom border at the row unfreezing mark (always strong)
1724 if (draw_strong_bottom_border && bg_y2 >= mTable.BgClipRect.Min.y && bg_y2 < mTable.BgClipRect.Max.y)
1725 window->DrawList->AddLine(ImVec2(mTable.BorderX1, bg_y2), ImVec2(mTable.BorderX2, bg_y2), mTable.BorderColorStrong, border_size);
1726 }
1727
1728 // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle)
1729 // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and
1730 // get the new cursor position.
1731 if (unfreeze_rows_request)
1732 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
1733 TableColumn* column = &mTable.Columns[column_n];
1734 column->NavLayerCurrent = (ImS8) ((column_n < mTable.FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
1735 }
1736 if (unfreeze_rows_actual) {
1737 IM_ASSERT(mTable.IsUnfrozenRows == false);
1738 mTable.IsUnfrozenRows = true;
1739
1740 // BgClipRect starts as mTable.InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect
1741 float y0 = ImMax(mTable.RowPosY2 + 1, window->InnerClipRect.Min.y);
1742 mTable.BgClipRect.Min.y = mTable.Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y);
1743 mTable.BgClipRect.Max.y = mTable.Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y;
1745 IM_ASSERT(mTable.Bg2ClipRectForDrawCmd.Min.y <= mTable.Bg2ClipRectForDrawCmd.Max.y);
1746
1747 float row_height = mTable.RowPosY2 - mTable.RowPosY1;
1748 mTable.RowPosY2 = window->DC.CursorPos.y = mTable.WorkRect.Min.y + mTable.RowPosY2 - mTable.OuterRect.Min.y;
1749 mTable.RowPosY1 = mTable.RowPosY2 - row_height;
1750 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
1751 TableColumn* column = &mTable.Columns[column_n];
1752 column->DrawChannelCurrent = column->DrawChannelUnfrozen;
1753 column->ClipRect.Min.y = mTable.Bg2ClipRectForDrawCmd.Min.y;
1754 }
1755
1756 // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y
1757 ImGui::SetWindowClipRectBeforeSetChannel(window, mTable.Columns[0].ClipRect);
1758 mTable.DrawSplitter.SetCurrentChannel(window->DrawList, mTable.Columns[0].DrawChannelCurrent);
1759 }
1760
1761 if (!(mTable.RowFlags & ImGuiTableRowFlags_Headers))
1762 mTable.RowBgColorCounter++;
1763 mTable.IsInsideRow = false;
1764 }
1765
1766 template<size_t MaxColumnCount>
1767 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1768 void MainTable<MaxColumnCount>::DrawBorders() {
1769 ImGuiWindow* inner_window = mTable.InnerWindow;
1770 if (!mTable.OuterWindow->ClipRect.Overlaps(mTable.OuterRect))
1771 return;
1772
1773 ImDrawList* inner_drawlist = inner_window->DrawList;
1774 mTable.DrawSplitter.SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0);
1775 inner_drawlist->PushClipRect(mTable.Bg0ClipRectForDrawCmd.Min, mTable.Bg0ClipRectForDrawCmd.Max, false);
1776
1777 // Draw inner border and resizing feedback
1778 const float border_size = TABLE_BORDER_SIZE;
1779 const float draw_y1 = mTable.InnerRect.Min.y;
1780 const float draw_y2_body = mTable.InnerRect.Max.y;
1781 const float draw_y2_head = mTable.IsUsingHeaders ? ImMin(mTable.InnerRect.Max.y, (mTable.FreezeRowsCount >= 1 ? mTable.InnerRect.Min.y : mTable.WorkRect.Min.y) + mTable.LastFirstRowHeight) : draw_y1;
1782 if (mTable.Flags & ImGuiTableFlags_BordersInnerV) {
1783 for (int order_n = 0; order_n < mTable.ColumnsCount; order_n++) {
1784 if (!mTable.EnabledMaskByDisplayOrder.test(order_n))
1785 continue;
1786
1787 const int column_n = mTable.DisplayOrderToIndex[order_n];
1788 TableColumn* column = &mTable.Columns[column_n];
1789 const bool is_hovered = (mTable.HoveredColumnBorder == column_n);
1790 const bool is_resized = (mTable.ResizedColumn == column_n) && (mTable.InstanceInteracted == mTable.InstanceCurrent);
1791 const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0;
1792 const bool is_frozen_separator = (mTable.FreezeColumnsCount != -1 && mTable.FreezeColumnsCount == order_n + 1);
1793 if (column->MaxX > mTable.InnerClipRect.Max.x && !is_resized)
1794 continue;
1795
1796 // Decide whether right-most column is visible
1797 if (column->NextEnabledColumn == -1 && !is_resizable)
1798 if ((mTable.Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame || (mTable.Flags & ImGuiTableFlags_NoHostExtendX))
1799 continue;
1800 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..
1801 continue;
1802
1803 // Draw in outer window so right-most column won't be clipped
1804 // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling.
1805 ImU32 col;
1806 float draw_y2;
1807 if (is_hovered || is_resized || is_frozen_separator) {
1808 draw_y2 = draw_y2_body;
1809 col = is_resized ? ImGui::GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? ImGui::GetColorU32(ImGuiCol_SeparatorHovered)
1810 : mTable.BorderColorStrong;
1811 } else {
1812 draw_y2 = (mTable.Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body;
1813 col = (mTable.Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? mTable.BorderColorStrong : mTable.BorderColorLight;
1814 }
1815
1816 if (draw_y2 > draw_y1)
1817 inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size);
1818 }
1819 }
1820
1821 // Draw outer border
1822 // FIXME: could use AddRect or explicit VLine/HLine helper?
1823 if (mTable.Flags & ImGuiTableFlags_BordersOuter) {
1824 // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call
1825 // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their
1826 // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part
1827 // of it in inner window, and the part that's over scrollbars in the outer window..)
1828 // Either solution currently won't allow us to use a larger border size: the border would clipped.
1829 const ImRect outer_border = mTable.OuterRect;
1830 const ImU32 outer_col = mTable.BorderColorStrong;
1831 if ((mTable.Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) {
1832 inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, ~0, border_size);
1833 } else if (mTable.Flags & ImGuiTableFlags_BordersOuterV) {
1834 inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size);
1835 inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size);
1836 } else if (mTable.Flags & ImGuiTableFlags_BordersOuterH) {
1837 inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size);
1838 inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size);
1839 }
1840 }
1841 if ((mTable.Flags & ImGuiTableFlags_BordersInnerH) && mTable.RowPosY2 < mTable.OuterRect.Max.y) {
1842 // Draw bottom-most row border
1843 const float border_y = mTable.RowPosY2;
1844 if (border_y >= mTable.BgClipRect.Min.y && border_y < mTable.BgClipRect.Max.y)
1845 inner_drawlist->AddLine(ImVec2(mTable.BorderX1, border_y), ImVec2(mTable.BorderX2, border_y), mTable.BorderColorLight, border_size);
1846 }
1847
1848 inner_drawlist->PopClipRect();
1849 }
1850
1851 template<size_t MaxColumnCount>
1852 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1853 void MainTable<MaxColumnCount>::MergeDrawChannels() {
1854 ImGuiContext& g = *GImGui;
1855 ImDrawListSplitter* splitter = &mTable.DrawSplitter;
1856 const bool has_freeze_v = (mTable.FreezeRowsCount > 0);
1857 const bool has_freeze_h = (mTable.FreezeColumnsCount > 0);
1858 IM_ASSERT(splitter->_Current == 0);
1859
1860 // Track which groups we are going to attempt to merge, and which channels goes into each group.
1861 struct MergeGroup {
1862 ImRect ClipRect;
1863 int ChannelsCount;
1864 ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> ChannelsMask;
1865 };
1866 int merge_group_mask = 0x00;
1867 MergeGroup merge_groups[4];
1868 memset(merge_groups, 0, sizeof(merge_groups));
1869
1870 // 1. Scan channels and take note of those which can be merged
1871 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
1872 if (!mTable.VisibleMaskByIndex.test(column_n))
1873 continue;
1874 TableColumn* column = &mTable.Columns[column_n];
1875
1876 const int merge_group_sub_count = has_freeze_v ? 2 : 1;
1877 for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++) {
1878 const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen;
1879
1880 // Don't attempt to merge if there are multiple draw calls within the column
1881 ImDrawChannel* src_channel = &splitter->_Channels[channel_no];
1882 if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0)
1883 src_channel->_CmdBuffer.pop_back();
1884 if (src_channel->_CmdBuffer.Size != 1)
1885 continue;
1886
1887 // Find out the width of this merge group and check if it will fit in our column
1888 // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it)
1889 if (!(column->Flags & ImGuiTableColumnFlags_NoClip)) {
1890 float content_max_x;
1891 if (!has_freeze_v)
1892 content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze
1893 else if (merge_group_sub_n == 0)
1894 content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed); // Row freeze: use width before freeze
1895 else
1896 content_max_x = column->ContentMaxXUnfrozen; // Row freeze: use width after freeze
1897 if (content_max_x > column->ClipRect.Max.x)
1898 continue;
1899 }
1900
1901 const int merge_group_n = (has_freeze_h && column_n < mTable.FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2);
1902 IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS);
1903 MergeGroup* merge_group = &merge_groups[merge_group_n];
1904 if (merge_group->ChannelsCount == 0)
1905 merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
1906 merge_group->ChannelsMask.SetBit(channel_no);
1907 merge_group->ChannelsCount++;
1908 merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect);
1909 merge_group_mask |= (1 << merge_group_n);
1910 }
1911
1912 // Invalidate current draw channel
1913 // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data)
1914 column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx) -1;
1915 }
1916
1917 // 2. Rewrite channel list in our preferred order
1918 if (merge_group_mask != 0) {
1919 // We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels().
1920 const int LEADING_DRAW_CHANNELS = 2;
1921 g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized
1922 ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data;
1923 ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> remaining_mask; // We need 132-bit of storage
1924 remaining_mask.ClearAllBits();
1925 remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count);
1926 remaining_mask.ClearBit(mTable.Bg2DrawChannelUnfrozen);
1927 IM_ASSERT(has_freeze_v == false || mTable.Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN);
1928 int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS);
1929 //ImRect host_rect = (mTable.InnerWindow == mTable.OuterWindow) ? mTable.InnerClipRect : mTable.HostClipRect;
1930 ImRect host_rect = mTable.HostClipRect;
1931 for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) {
1932 if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) {
1933 MergeGroup* merge_group = &merge_groups[merge_group_n];
1934 ImRect merge_clip_rect = merge_group->ClipRect;
1935
1936 // Extend outer-most clip limits to match those of host, so draw calls can be merged even if
1937 // outer-most columns have some outer padding offsetting them from their parent ClipRect.
1938 // The principal cases this is dealing with are:
1939 // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge
1940 // - 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
1941 // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit
1942 // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect.
1943 if ((merge_group_n & 1) == 0 || !has_freeze_h)
1944 merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x);
1945 if ((merge_group_n & 2) == 0 || !has_freeze_v)
1946 merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y);
1947 if ((merge_group_n & 1) != 0)
1948 merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x);
1949 if ((merge_group_n & 2) != 0 && (mTable.Flags & ImGuiTableFlags_NoHostExtendY) == 0)
1950 merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y);
1951
1952 remaining_count -= merge_group->ChannelsCount;
1953 for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++)
1954 remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n];
1955 for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) {
1956 // Copy + overwrite new clip rect
1957 if (!merge_group->ChannelsMask.TestBit(n))
1958 continue;
1959 merge_group->ChannelsMask.ClearBit(n);
1960 merge_channels_count--;
1961
1962 ImDrawChannel* channel = &splitter->_Channels[n];
1963 IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect)));
1964 channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4();
1965 memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));
1966 }
1967 }
1968
1969 // Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1)
1970 if (merge_group_n == 1 && has_freeze_v)
1971 memcpy(dst_tmp++, &splitter->_Channels[mTable.Bg2DrawChannelUnfrozen], sizeof(ImDrawChannel));
1972 }
1973
1974 // Append unmergeable channels that we didn't reorder at the end of the list
1975 for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) {
1976 if (!remaining_mask.TestBit(n))
1977 continue;
1978 ImDrawChannel* channel = &splitter->_Channels[n];
1979 memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));
1980 remaining_count--;
1981 }
1982 IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size);
1983 memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel));
1984 }
1985 }
1986
1987 template<size_t MaxColumnCount>
1988 requires SmallerThanMaxColumnAmount<MaxColumnCount>
1989 float MainTable<MaxColumnCount>::GetColumnWidthAuto(TableColumn* column) {
1990 const float content_width_body = ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX;
1991 const float content_width_headers = column->ContentMaxXHeadersIdeal - column->WorkMinX;
1992 float width_auto = content_width_body;
1993 if (!(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth))
1994 width_auto = ImMax(width_auto, content_width_headers);
1995
1996 // Non-resizable fixed columns preserve their requested width
1997 if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f)
1998 if (!(mTable.Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize))
1999 width_auto = column->InitStretchWeightOrWidth;
2000
2001 return ImMax(width_auto, mTable.MinColumnWidth);
2002 }
2003
2004 template<size_t MaxColumnCount>
2005 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2006 void MainTable<MaxColumnCount>::SaveSettingsCustom() {
2007 mTable.IsSettingsDirty = false;
2008 if (mTable.Flags & ImGuiTableFlags_NoSavedSettings || (getCustomColumnsActive() && mSpecificColumnsActive))
2009 return;
2010
2011 // Bind or create settings data
2012 TableSettings& settings = getTableSettings();
2013
2014 // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings
2015 // IM_ASSERT(settings.ID == mTable.ID);
2016 // IM_ASSERT(settings->ColumnsCount == mTable.ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount);
2017 TableColumn* column = mTable.Columns.Data;
2018
2019 // clear columns before saving them
2020 settings.Columns.clear();
2021
2022 bool save_ref_scale = false;
2023 settings.SaveFlags = ImGuiTableFlags_None;
2024 for (int n = 0; n < mTable.ColumnsCount; n++, column++) {
2025 TableColumnSettings& column_settings = settings.Columns.emplace_back();
2026 const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest;
2027 column_settings.WidthOrWeight = width_or_weight;
2028 column_settings.UserID = column->UserID;
2029 // column_settings.Index = (TableColumnIdx)n;
2030 column_settings.DisplayOrder = column->DisplayOrder;
2031 column_settings.SortOrder = column->SortOrder;
2032 column_settings.SortDirection = column->SortDirection;
2033 column_settings.IsEnabled = column->IsEnabled;
2034 column_settings.IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0;
2035 if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0)
2036 save_ref_scale = true;
2037
2038 // We skip saving some data in the .ini file when they are unnecessary to restore our state.
2039 // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f.
2040 // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present.
2041 if (width_or_weight != column->InitStretchWeightOrWidth)
2042 settings.SaveFlags |= ImGuiTableFlags_Resizable;
2043 if (column->DisplayOrder != n)
2044 settings.SaveFlags |= ImGuiTableFlags_Reorderable;
2045 if (column->SortOrder != -1)
2046 settings.SaveFlags |= ImGuiTableFlags_Sortable;
2047 if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0))
2048 settings.SaveFlags |= ImGuiTableFlags_Hideable;
2049 }
2050 settings.SaveFlags &= mTable.Flags;
2051 settings.RefScale = save_ref_scale ? mTable.RefScale : 0.0f;
2052 }
2053
2054 template<size_t MaxColumnCount>
2055 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2056 void MainTable<MaxColumnCount>::MigrateIniSettings() {
2057 bool backup = mSpecificColumnsUpdate;
2058 mSpecificColumnsActive = false;
2059
2060 TableSettings& tableSettings = getTableSettings();
2061
2062 // move all settings to the new object, without changing the Table object
2063 ImGuiContext& g = *GImGui;
2064
2065 // Bind settings
2066 ImGuiTableSettings* settings;
2067 if (mTable.SettingsOffset == -1) {
2068 settings = ImGui::TableSettingsFindByID(mTable.ID);
2069 if (settings == NULL)
2070 return;
2071 mTable.SettingsOffset = g.SettingsTables.offset_from_ptr(settings);
2072 } else {
2073 settings = GetBoundSettings();
2074 }
2075
2076 tableSettings.SaveFlags = settings->SaveFlags;
2077 tableSettings.RefScale = settings->RefScale;
2078
2079 tableSettings.Columns.resize(settings->ColumnsCount);
2080
2081 // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
2082 ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
2083 for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) {
2084 int column_n = column_settings->Index;
2085 if (column_n < 0 || column_n >= mTable.ColumnsCount)
2086 continue;
2087
2088 auto& column = tableSettings.Columns[column_n];
2089
2090 column.DisplayOrder = column_settings->DisplayOrder;
2091 column.SortOrder = column_settings->SortOrder;
2092 column.IsEnabled = column_settings->IsEnabled;
2093 column.IsStretch = column_settings->IsStretch;
2094 column.SortDirection = column_settings->SortDirection;
2095 column.UserID = column_settings->UserID;
2096 column.WidthOrWeight = column_settings->WidthOrWeight;
2097 }
2098
2099 mSpecificColumnsActive = backup;
2100 }
2101
2102 template<size_t MaxColumnCount>
2103 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2104 void MainTable<MaxColumnCount>::SetupColumnFlags(TableColumn* column, ImGuiTableColumnFlags flags_in) {
2105 ImGuiTableColumnFlags flags = flags_in;
2106
2107 // Sizing Policy
2108 if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) {
2109 const ImGuiTableFlags table_sizing_policy = (mTable.Flags & ImGuiTableFlags_SizingMask_);
2110 if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame)
2111 flags |= ImGuiTableColumnFlags_WidthFixed;
2112 else
2113 flags |= ImGuiTableColumnFlags_WidthStretch;
2114 } else {
2115 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used.
2116 }
2117
2118 // Resize
2119 if ((mTable.Flags & ImGuiTableFlags_Resizable) == 0)
2120 flags |= ImGuiTableColumnFlags_NoResize;
2121
2122 // Sorting
2123 if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending))
2124 flags |= ImGuiTableColumnFlags_NoSort;
2125
2126 // Indentation
2127 if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0)
2128 flags |= (mTable.Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable;
2129
2130 // Alignment
2131 //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0)
2132 // flags |= ImGuiTableColumnFlags_AlignCenter;
2133 //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used.
2134
2135 // Preserve status flags
2136 column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_);
2137
2138 // Build an ordered list of available sort directions
2139 column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0;
2140 if (mTable.Flags & ImGuiTableFlags_Sortable) {
2141 int count = 0, mask = 0, list = 0;
2142 if ((flags & ImGuiTableColumnFlags_PreferSortAscending) != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) {
2143 mask |= 1 << ImGuiSortDirection_Ascending;
2144 list |= ImGuiSortDirection_Ascending << (count << 1);
2145 count++;
2146 }
2147 if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) {
2148 mask |= 1 << ImGuiSortDirection_Descending;
2149 list |= ImGuiSortDirection_Descending << (count << 1);
2150 count++;
2151 }
2152 if ((flags & ImGuiTableColumnFlags_PreferSortAscending) == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) {
2153 mask |= 1 << ImGuiSortDirection_Ascending;
2154 list |= ImGuiSortDirection_Ascending << (count << 1);
2155 count++;
2156 }
2157 if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) {
2158 mask |= 1 << ImGuiSortDirection_Descending;
2159 list |= ImGuiSortDirection_Descending << (count << 1);
2160 count++;
2161 }
2162 if ((mTable.Flags & ImGuiTableFlags_SortTristate) || count == 0) {
2163 mask |= 1 << ImGuiSortDirection_None;
2164 count++;
2165 }
2166 column->SortDirectionsAvailList = (ImU8) list;
2167 column->SortDirectionsAvailMask = (ImU8) mask;
2168 column->SortDirectionsAvailCount = (ImU8) count;
2169 FixColumnSortDirection(column);
2170 }
2171 }
2172
2173 template<size_t MaxColumnCount>
2174 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2175 float MainTable<MaxColumnCount>::GetMaxColumnWidth(int column_n) {
2176 const TableColumn* column = &mTable.Columns[column_n];
2177 float max_width = FLT_MAX;
2178 const float min_column_distance = mTable.MinColumnWidth + mTable.CellPaddingX * 2.0f + mTable.CellSpacingX1 + mTable.CellSpacingX2;
2179 if (mTable.Flags & ImGuiTableFlags_ScrollX) {
2180 // Frozen columns can't reach beyond visible width else scrolling will naturally break.
2181 if (column->DisplayOrder < mTable.FreezeColumnsRequest) {
2182 max_width = (mTable.InnerClipRect.Max.x - (mTable.FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX;
2183 max_width = max_width - mTable.OuterPaddingX - mTable.CellPaddingX - mTable.CellSpacingX2;
2184 }
2185 } else if ((mTable.Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0) {
2186 // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make
2187 // sure they are all visible. Because of this we also know that all of the columns will always fit in
2188 // mTable.WorkRect and therefore in mTable.InnerRect (because ScrollX is off)
2189 // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match.
2190 // See "table_width_distrib" and "table_width_keep_visible" tests
2191 max_width = mTable.WorkRect.Max.x - (mTable.ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - column->MinX;
2192 //max_width -= mTable.CellSpacingX1;
2193 max_width -= mTable.CellSpacingX2;
2194 max_width -= mTable.CellPaddingX * 2.0f;
2195 max_width -= mTable.OuterPaddingX;
2196 }
2197 return max_width;
2198 }
2199
2200 template<size_t MaxColumnCount>
2201 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2202 void MainTable<MaxColumnCount>::SetupDrawChannels() {
2203 const int freeze_row_multiplier = (mTable.FreezeRowsCount > 0) ? 2 : 1;
2204 const int channels_for_row = (mTable.Flags & ImGuiTableFlags_NoClip) ? 1 : mTable.ColumnsEnabledCount;
2205 const int channels_for_bg = 1 + 1 * freeze_row_multiplier;
2206 const int channels_for_dummy = (mTable.ColumnsEnabledCount < mTable.ColumnsCount || mTable.VisibleMaskByIndex != mTable.EnabledMaskByIndex) ? +1 : 0;
2207 const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy;
2208 mTable.DrawSplitter.Split(mTable.InnerWindow->DrawList, channels_total);
2209 mTable.DummyDrawChannel = (ImGuiTableDrawChannelIdx) ((channels_for_dummy > 0) ? channels_total - 1 : -1);
2210 mTable.Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN;
2211 mTable.Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx) ((mTable.FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN);
2212
2213 int draw_channel_current = 2;
2214 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
2215 TableColumn* column = &mTable.Columns[column_n];
2216 if (column->IsVisibleX && column->IsVisibleY) {
2217 column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx) (draw_channel_current);
2218 column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx) (draw_channel_current + (mTable.FreezeRowsCount > 0 ? channels_for_row + 1 : 0));
2219 if (!(mTable.Flags & ImGuiTableFlags_NoClip))
2220 draw_channel_current++;
2221 } else {
2222 column->DrawChannelFrozen = column->DrawChannelUnfrozen = mTable.DummyDrawChannel;
2223 }
2224 column->DrawChannelCurrent = column->DrawChannelFrozen;
2225 }
2226
2227 // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default.
2228 // All our cell highlight are manually clipped with BgClipRect. When unfreezing it will be made smaller to fit scrolling rect.
2229 // (This technically isn't part of setting up draw channels, but is reasonably related to be done here)
2230 mTable.BgClipRect = mTable.InnerClipRect;
2231 mTable.Bg0ClipRectForDrawCmd = mTable.OuterWindow->ClipRect;
2232 mTable.Bg2ClipRectForDrawCmd = mTable.HostClipRect;
2233 IM_ASSERT(mTable.BgClipRect.Min.y <= mTable.BgClipRect.Max.y);
2234 }
2235
2236 template<size_t MaxColumnCount>
2237 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2238 void MainTable<MaxColumnCount>::UpdateBorders() {
2239 ImGuiContext& g = *GImGui;
2240 IM_ASSERT(mTable.Flags & ImGuiTableFlags_Resizable);
2241
2242 // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and
2243 // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not
2244 // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height).
2245 // Actual columns highlight/render will be performed in EndTable() and not be affected.
2246 const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS;
2247 const float hit_y1 = mTable.OuterRect.Min.y;
2248 const float hit_y2_body = ImMax(mTable.OuterRect.Max.y, hit_y1 + mTable.LastOuterHeight);
2249 const float hit_y2_head = hit_y1 + mTable.LastFirstRowHeight;
2250
2251 for (int order_n = 0; order_n < mTable.ColumnsCount; order_n++) {
2252 if (!mTable.EnabledMaskByDisplayOrder.test(order_n))
2253 continue;
2254
2255 const int column_n = mTable.DisplayOrderToIndex[order_n];
2256 TableColumn* column = &mTable.Columns[column_n];
2257 if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_))
2258 continue;
2259
2260 // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders()
2261 const float border_y2_hit = (mTable.Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body;
2262 if ((mTable.Flags & ImGuiTableFlags_NoBordersInBody) && mTable.IsUsingHeaders == false)
2263 continue;
2264
2265 if (mTable.FreezeColumnsCount > 0)
2266 if (column->MaxX < mTable.Columns[mTable.DisplayOrderToIndex[mTable.FreezeColumnsCount - 1]].MaxX)
2267 continue;
2268
2269 ImGuiID column_id = GetColumnResizeID(column_n, mTable.InstanceCurrent);
2270 ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit);
2271 //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100));
2272 ImGui::KeepAliveID(column_id);
2273
2274 bool hovered = false, held = false;
2275 bool pressed = ImGui::ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);
2276 if (pressed && ImGui::IsMouseDoubleClicked(0)) {
2277 SetColumnWidthAutoSingle(column_n);
2278 ImGui::ClearActiveID();
2279 held = hovered = false;
2280 }
2281 if (held) {
2282 if (mTable.LastResizedColumn == -1)
2283 mTable.ResizeLockMinContentsX2 = mTable.RightMostEnabledColumn != -1 ? mTable.Columns[mTable.RightMostEnabledColumn].MaxX : -FLT_MAX;
2284 mTable.ResizedColumn = (TableColumnIdx) column_n;
2285 mTable.InstanceInteracted = mTable.InstanceCurrent;
2286 }
2287 if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) {
2288 mTable.HoveredColumnBorder = (TableColumnIdx) column_n;
2289 ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
2290 }
2291 }
2292 }
2293
2294 template<size_t MaxColumnCount>
2295 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2296 void MainTable<MaxColumnCount>::SortSpecsSanitize() {
2297 IM_ASSERT(mTable.Flags & ImGuiTableFlags_Sortable);
2298
2299 // Clear SortOrder from hidden column and verify that there's no gap or duplicate.
2300 int sort_order_count = 0;
2301 ColumnBitMask sort_order_mask;
2302 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
2303 TableColumn* column = &mTable.Columns[column_n];
2304 if (column->SortOrder != -1 && !column->IsEnabled)
2305 column->SortOrder = -1;
2306 if (column->SortOrder == -1)
2307 continue;
2308 sort_order_count++;
2309 sort_order_mask.set(column->SortOrder);
2310 }
2311
2312 ColumnBitMask expected;
2313 for (int i = 0; i < sort_order_count; ++i) {
2314 expected.set(i);
2315 }
2316
2317 const bool need_fix_linearize = expected != sort_order_mask;
2318 const bool need_fix_single_sort_order = (sort_order_count > 1) && !(mTable.Flags & ImGuiTableFlags_SortMulti);
2319 if (need_fix_linearize || need_fix_single_sort_order) {
2320 ColumnBitMask fixed_mask;
2321 for (int sort_n = 0; sort_n < sort_order_count; sort_n++) {
2322 // Fix: Rewrite sort order fields if needed so they have no gap or duplicate.
2323 // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1)
2324 int column_with_smallest_sort_order = -1;
2325 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++)
2326 if (!fixed_mask.test(column_n) && mTable.Columns[column_n].SortOrder != -1)
2327 if (column_with_smallest_sort_order == -1 || mTable.Columns[column_n].SortOrder < mTable.Columns[column_with_smallest_sort_order].SortOrder)
2328 column_with_smallest_sort_order = column_n;
2329 IM_ASSERT(column_with_smallest_sort_order != -1);
2330 fixed_mask.set(column_with_smallest_sort_order);
2331 mTable.Columns[column_with_smallest_sort_order].SortOrder = (TableColumnIdx) sort_n;
2332
2333 // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set.
2334 if (need_fix_single_sort_order) {
2335 sort_order_count = 1;
2336 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++)
2337 if (column_n != column_with_smallest_sort_order)
2338 mTable.Columns[column_n].SortOrder = -1;
2339 break;
2340 }
2341 }
2342 }
2343
2344 // Fallback default sort order (if no column had the ImGuiTableColumnFlags_DefaultSort flag)
2345 if (sort_order_count == 0 && !(mTable.Flags & ImGuiTableFlags_SortTristate))
2346 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
2347 TableColumn* column = &mTable.Columns[column_n];
2348 if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort)) {
2349 sort_order_count = 1;
2350 column->SortOrder = 0;
2351 column->SortDirection = (ImU8) GetColumnAvailSortDirection(column, 0);
2352 break;
2353 }
2354 }
2355
2356 mTable.SortSpecsCount = (TableColumnIdx) sort_order_count;
2357 }
2358
2359 template<size_t MaxColumnCount>
2360 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2361 void MainTable<MaxColumnCount>::FixColumnSortDirection(TableColumn* column) {
2362 if (column->SortOrder == -1 || (column->SortDirectionsAvailMask & (1 << column->SortDirection)) != 0)
2363 return;
2364 column->SortDirection = (ImU8) GetColumnAvailSortDirection(column, 0);
2365 mTable.IsSortSpecsDirty = true;
2366 }
2367
2368 template<size_t MaxColumnCount>
2369 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2370 void MainTable<MaxColumnCount>::EndCell() {
2371 TableColumn* column = &mTable.Columns[mTable.CurrentColumn];
2372 ImGuiWindow* window = mTable.InnerWindow;
2373
2374 // Report maximum position so we can infer content size per column.
2375 float* p_max_pos_x;
2376 if (mTable.RowFlags & ImGuiTableRowFlags_Headers)
2377 p_max_pos_x = &column->ContentMaxXHeadersUsed; // Useful in case user submit contents in header row that is not a TableHeader() call
2378 else
2379 p_max_pos_x = mTable.IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen;
2380 *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x);
2381 mTable.RowPosY2 = ImMax(mTable.RowPosY2, window->DC.CursorMaxPos.y + mTable.CellPaddingY);
2382 column->ItemWidth = window->DC.ItemWidth;
2383
2384 // Propagate text baseline for the entire row
2385 // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one.
2386 mTable.RowTextBaseline = ImMax(mTable.RowTextBaseline, window->DC.PrevLineTextBaseOffset);
2387 }
2388
2389 template<size_t MaxColumnCount>
2390 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2391 void MainTable<MaxColumnCount>::BeginCell(int column_n) {
2392 TableColumn* column = &mTable.Columns[column_n];
2393 ImGuiWindow* window = mTable.InnerWindow;
2394 mTable.CurrentColumn = column_n;
2395
2396 // Start position is roughly ~~ CellRect.Min + CellPadding + Indent
2397 float start_x = column->WorkMinX;
2398 if (column->Flags & ImGuiTableColumnFlags_IndentEnable)
2399 start_x += mTable.RowIndentOffsetX; // ~~ += window.DC.Indent.x - mTable.HostIndentX, except we locked it for the row.
2400
2401 window->DC.CursorPos.x = start_x;
2402 window->DC.CursorPos.y = mTable.RowPosY1 + mTable.CellPaddingY;
2403 window->DC.CursorMaxPos.x = window->DC.CursorPos.x;
2404 window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT
2405 window->DC.CurrLineTextBaseOffset = mTable.RowTextBaseline;
2406 window->DC.NavLayerCurrent = (ImGuiNavLayer) column->NavLayerCurrent;
2407
2408 window->WorkRect.Min.y = window->DC.CursorPos.y;
2409 window->WorkRect.Min.x = column->WorkMinX;
2410 window->WorkRect.Max.x = column->WorkMaxX;
2411 window->DC.ItemWidth = column->ItemWidth;
2412
2413 // To allow ImGuiListClipper to function we propagate our row height
2414 if (!column->IsEnabled)
2415 window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, mTable.RowPosY2);
2416
2417 window->SkipItems = column->IsSkipItems;
2418 if (column->IsSkipItems) {
2419 window->DC.LastItemId = 0;
2420 window->DC.LastItemStatusFlags = 0;
2421 }
2422
2423 if (mTable.Flags & ImGuiTableFlags_NoClip) {
2424 // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.
2425 mTable.DrawSplitter.SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);
2426 //IM_ASSERT(mTable.DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP);
2427 } else {
2428 // FIXME-TABLE: Could avoid this if draw channel is dummy channel?
2429 ImGui::SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
2430 mTable.DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
2431 }
2432 }
2433
2434 template<size_t MaxColumnCount>
2435 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2436 void MainTable<MaxColumnCount>::BeginRow() {
2437 ImGuiWindow* window = mTable.InnerWindow;
2438 IM_ASSERT(!mTable.IsInsideRow);
2439
2440 // New row
2441 mTable.CurrentRow++;
2442 mTable.CurrentColumn = -1;
2443 mTable.RowBgColor[0] = mTable.RowBgColor[1] = IM_COL32_DISABLE;
2444 mTable.RowCellDataCurrent = -1;
2445 mTable.IsInsideRow = true;
2446
2447 // Begin frozen rows
2448 float next_y1 = mTable.RowPosY2;
2449 if (mTable.CurrentRow == 0 && mTable.FreezeRowsCount > 0)
2450 next_y1 = window->DC.CursorPos.y = mTable.OuterRect.Min.y;
2451
2452 mTable.RowPosY1 = mTable.RowPosY2 = next_y1;
2453 mTable.RowTextBaseline = 0.0f;
2454 mTable.RowIndentOffsetX = window->DC.Indent.x - mTable.HostIndentX; // Lock indent
2455 window->DC.PrevLineTextBaseOffset = 0.0f;
2456 window->DC.CursorMaxPos.y = next_y1;
2457
2458 // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging.
2459 if (mTable.RowFlags & ImGuiTableRowFlags_Headers) {
2460 SetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImGuiCol_TableHeaderBg));
2461 if (mTable.CurrentRow == 0)
2462 mTable.IsUsingHeaders = true;
2463 }
2464 }
2465
2466 template<size_t MaxColumnCount>
2467 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2468 void MainTable<MaxColumnCount>::SetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n) {
2469 IM_ASSERT(target != ImGuiTableBgTarget_None);
2470
2471 if (color == IM_COL32_DISABLE)
2472 color = 0;
2473
2474 // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time.
2475 switch (target) {
2476 case ImGuiTableBgTarget_CellBg: {
2477 if (mTable.RowPosY1 > mTable.InnerClipRect.Max.y) // Discard
2478 return;
2479 if (column_n == -1)
2480 column_n = mTable.CurrentColumn;
2481 if (!mTable.VisibleMaskByIndex.test(column_n))
2482 return;
2483 if (mTable.RowCellDataCurrent < 0 || mTable.RowCellData[mTable.RowCellDataCurrent].Column != column_n)
2484 mTable.RowCellDataCurrent++;
2485 ImGuiTableCellData* cell_data = &mTable.RowCellData[mTable.RowCellDataCurrent];
2486 cell_data->BgColor = color;
2487 cell_data->Column = (TableColumnIdx) column_n;
2488 break;
2489 }
2490 case ImGuiTableBgTarget_RowBg0:
2491 case ImGuiTableBgTarget_RowBg1: {
2492 if (mTable.RowPosY1 > mTable.InnerClipRect.Max.y) // Discard
2493 return;
2494 IM_ASSERT(column_n == -1);
2495 int bg_idx = (target == ImGuiTableBgTarget_RowBg1) ? 1 : 0;
2496 mTable.RowBgColor[bg_idx] = color;
2497 break;
2498 }
2499 default:
2500 IM_ASSERT(0);
2501 }
2502 }
2503
2504 template<size_t MaxColumnCount>
2505 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2506 void MainTable<MaxColumnCount>::SetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs) {
2507 if (!(mTable.Flags & ImGuiTableFlags_SortMulti))
2508 append_to_sort_specs = false;
2509 if (!(mTable.Flags & ImGuiTableFlags_SortTristate))
2510 IM_ASSERT(sort_direction != ImGuiSortDirection_None);
2511
2512 TableColumnIdx sort_order_max = 0;
2513 if (append_to_sort_specs)
2514 for (int other_column_n = 0; other_column_n < mTable.ColumnsCount; other_column_n++)
2515 sort_order_max = ImMax(sort_order_max, mTable.Columns[other_column_n].SortOrder);
2516
2517 TableColumn* column = &mTable.Columns[column_n];
2518 column->SortDirection = (ImU8) sort_direction;
2519 if (column->SortDirection == ImGuiSortDirection_None)
2520 column->SortOrder = -1;
2521 else if (column->SortOrder == -1 || !append_to_sort_specs)
2522 column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0;
2523
2524 for (int other_column_n = 0; other_column_n < mTable.ColumnsCount; other_column_n++) {
2525 TableColumn* other_column = &mTable.Columns[other_column_n];
2526 if (other_column != column && !append_to_sort_specs)
2527 other_column->SortOrder = -1;
2528 FixColumnSortDirection(other_column);
2529 }
2530 mTable.IsSettingsDirty = true;
2531 mTable.IsSortSpecsDirty = true;
2532 }
2533
2534 template<size_t MaxColumnCount>
2535 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2536 ImGuiSortDirection MainTable<MaxColumnCount>::GetColumnNextSortDirection(TableColumn* column) {
2537 IM_ASSERT(column->SortDirectionsAvailCount > 0);
2538 if (column->SortOrder == -1)
2539 return GetColumnAvailSortDirection(column, 0);
2540 for (int n = 0; n < 3; n++)
2541 if (column->SortDirection == GetColumnAvailSortDirection(column, n))
2542 return GetColumnAvailSortDirection(column, (n + 1) % column->SortDirectionsAvailCount);
2543 IM_ASSERT(0);
2544 return ImGuiSortDirection_None;
2545 }
2546
2547 template<size_t MaxColumnCount>
2548 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2549 void MainTable<MaxColumnCount>::SetColumnWidth(int column_n, float width) {
2550 IM_ASSERT(mTable.IsLayoutLocked == false);
2551 IM_ASSERT(column_n >= 0 && column_n < mTable.ColumnsCount);
2552 TableColumn* column_0 = &mTable.Columns[column_n];
2553 float column_0_width = width;
2554
2555 // Apply constraints early
2556 // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded)
2557 IM_ASSERT(mTable.MinColumnWidth > 0.0f);
2558 const float min_width = mTable.MinColumnWidth;
2559 const float max_width = ImMax(min_width, GetMaxColumnWidth(column_n));
2560 column_0_width = ImClamp(column_0_width, min_width, max_width);
2561 if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width)
2562 return;
2563
2564 //IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width);
2565 TableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &mTable.Columns[column_0->NextEnabledColumn] : NULL;
2566
2567 // In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns.
2568 // - All fixed: easy.
2569 // - All stretch: easy.
2570 // - One or more fixed + one stretch: easy.
2571 // - One or more fixed + more than one stretch: tricky.
2572 // Qt when manual resize is enabled only support a single _trailing_ stretch column.
2573
2574 // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1.
2575 // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user.
2576 // Scenarios:
2577 // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset.
2578 // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered.
2579 // - 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.
2580 // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1)
2581 // - W1 W2 W3 resize from W1| or W2| --> ok
2582 // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1)
2583 // - W1 F2 F3 resize from F3| --> ok: no-op (disabled by Resize Rule 1)
2584 // - W1 F2 resize from F2| --> ok: no-op (disabled by Resize Rule 1)
2585 // - W1 W2 F3 resize from W1| or W2| --> ok
2586 // - W1 F2 W3 resize from W1| or F2| --> ok
2587 // - F1 W2 F3 resize from W2| --> ok
2588 // - F1 W3 F2 resize from W3| --> ok
2589 // - W1 F2 F3 resize from W1| --> ok: equivalent to resizing |F2. F3 will not move.
2590 // - W1 F2 F3 resize from F2| --> ok
2591 // All resizes from a Wx columns are locking other columns.
2592
2593 // Possible improvements:
2594 // - 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.
2595 // - 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.
2596
2597 // [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout().
2598
2599 // 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.
2600 // This is the preferred resize path
2601 if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed)
2602 if (!column_1 || mTable.LeftMostStretchedColumn == -1 || mTable.Columns[mTable.LeftMostStretchedColumn].DisplayOrder >= column_0->DisplayOrder) {
2603 column_0->WidthRequest = column_0_width;
2604 mTable.IsSettingsDirty = true;
2605 return;
2606 }
2607
2608 // 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)
2609 if (column_1 == NULL)
2610 column_1 = (column_0->PrevEnabledColumn != -1) ? &mTable.Columns[column_0->PrevEnabledColumn] : NULL;
2611 if (column_1 == NULL)
2612 return;
2613
2614 // Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column.
2615 // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)
2616 float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width);
2617 column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width;
2618 IM_ASSERT(column_0_width > 0.0f && column_1_width > 0.0f);
2619 column_0->WidthRequest = column_0_width;
2620 column_1->WidthRequest = column_1_width;
2621 if ((column_0->Flags | column_1->Flags) & ImGuiTableColumnFlags_WidthStretch)
2622 UpdateColumnsWeightFromWidth();
2623 mTable.IsSettingsDirty = true;
2624 }
2625
2626 template<size_t MaxColumnCount>
2627 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2628 void MainTable<MaxColumnCount>::UpdateColumnsWeightFromWidth() {
2629 IM_ASSERT(mTable.LeftMostStretchedColumn != -1 && mTable.RightMostStretchedColumn != -1);
2630
2631 // Measure existing quantity
2632 float visible_weight = 0.0f;
2633 float visible_width = 0.0f;
2634 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
2635 TableColumn* column = &mTable.Columns[column_n];
2636 if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))
2637 continue;
2638 IM_ASSERT(column->StretchWeight > 0.0f);
2639 visible_weight += column->StretchWeight;
2640 visible_width += column->WidthRequest;
2641 }
2642 IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f);
2643
2644 // Apply new weights
2645 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++) {
2646 TableColumn* column = &mTable.Columns[column_n];
2647 if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))
2648 continue;
2649 column->StretchWeight = (column->WidthRequest / visible_width) * visible_weight;
2650 IM_ASSERT(column->StretchWeight > 0.0f);
2651 }
2652 }
2653
2654 template<size_t MaxColumnCount>
2655 requires SmallerThanMaxColumnAmount<MaxColumnCount>
2657 const ImRect& cellBgRect = GetCellBgRect(mTable.CurrentColumn);
2658
2659 // [[ DEBUG ]]
2660 // ImGui::GetCurrentWindow()->DrawList->AddRect(cellBgRect.Min, cellBgRect.Max), 0xff0000ff);
2661
2662 return ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(cellBgRect.Min, cellBgRect.Max);
2663 }
2664
2665 template<size_t MaxColumnCount>
2670
2671 template<size_t MaxColumnCount>
2676
2677 template<size_t MaxColumnCount>
2679 bool MainTable<MaxColumnCount>::Begin(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width, ImGuiWindowFlags child_window_flags) {
2680 ImGuiID id = ImGui::GetID(str_id);
2681
2682 // add ScrollY, we want to have scrollbars
2683 if (mFlags & MainTableFlags_SubWindow)
2684 flags |= ImGuiTableFlags_ScrollY;
2685
2686 ImGuiContext& g = *GImGui;
2687 ImGuiWindow* outer_window = ImGui::GetCurrentWindow();
2688 if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible.
2689 return false;
2690
2691 // Sanity checks
2692 IM_ASSERT(columns_count > 0 && columns_count <= MaxColumnCount && "Only 1..MaxColumnCount columns allowed!");
2693 if (flags & ImGuiTableFlags_ScrollX)
2694 IM_ASSERT(inner_width >= 0.0f);
2695
2696 // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping rules may evolve.
2697 const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0;
2698 const ImVec2 avail_size = ImGui::GetContentRegionAvail();
2699 ImVec2 actual_outer_size = ImGui::CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f);
2700 ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size);
2701 if (use_child_window && ImGui::IsClippedEx(outer_rect, 0, false)) {
2702 ImGui::ItemSize(outer_rect);
2703 return false;
2704 }
2705
2706 // Acquire storage for the table
2707 const int instance_no = (mTable.LastFrameActive != g.FrameCount) ? 0 : mTable.InstanceCurrent + 1;
2708 const ImGuiID instance_id = id + instance_no;
2709 const ImGuiTableFlags table_last_flags = mTable.Flags;
2710 if (instance_no > 0)
2711 IM_ASSERT(mTable.ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID");
2712
2713 // Fix flags
2714 mTable.IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0;
2715 flags = TableFixFlags(flags, outer_window);
2716
2717 // Initialize
2718 mTable.ID = id;
2719 mTable.Flags = flags;
2720 mTable.InstanceCurrent = (ImS16) instance_no;
2721 mTable.LastFrameActive = g.FrameCount;
2722 mTable.OuterWindow = mTable.InnerWindow = outer_window;
2723 mTable.ColumnsCount = columns_count;
2724 mTable.IsLayoutLocked = false;
2725 mTable.InnerWidth = inner_width;
2726 mTable.UserOuterSize = outer_size;
2727
2728 // When not using a child window, WorkRect.Max will grow as we append contents.
2729 if (use_child_window) {
2730 // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent
2731 // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing)
2732 ImVec2 override_content_size(FLT_MAX, FLT_MAX);
2733 if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY))
2734 override_content_size.y = FLT_MIN;
2735
2736 // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and
2737 // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align
2738 // based on the right side of the child window work rect, which would require knowing ahead if we are going to
2739 // have decoration taking horizontal spaces (typically a vertical scrollbar).
2740 if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f)
2741 override_content_size.x = inner_width;
2742
2743 if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX)
2744 ImGui::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));
2745
2746 // Reset scroll if we are reactivating it
2747 if ((table_last_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0)
2748 ImGui::SetNextWindowScroll(ImVec2(0.0f, 0.0f));
2749
2750 // Create scrolling region (without border and zero window padding)
2751 ImGuiWindowFlags child_flags = child_window_flags;
2752 child_flags |= getShowScrollbar() ? 0 : ImGuiWindowFlags_NoScrollbar;
2753 child_flags |= (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None;
2754 ImGui::BeginChildEx(str_id, instance_id, outer_rect.GetSize(), false, child_flags);
2755 mTable.InnerWindow = g.CurrentWindow;
2756 mTable.WorkRect = mTable.InnerWindow->WorkRect;
2757 mTable.OuterRect = mTable.InnerWindow->Rect();
2758 mTable.InnerRect = mTable.InnerWindow->InnerRect;
2759 IM_ASSERT(mTable.InnerWindow->WindowPadding.x == 0.0f && mTable.InnerWindow->WindowPadding.y == 0.0f && mTable.InnerWindow->WindowBorderSize == 0.0f);
2760 } else {
2761 // For non-scrolling tables, WorkRect == OuterRect == InnerRect.
2762 // 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().
2763 mTable.WorkRect = mTable.OuterRect = mTable.InnerRect = outer_rect;
2764 }
2765
2766 // Push a standardized ID for both child-using and not-child-using tables
2767 ImGui::PushOverrideID(instance_id);
2768
2769 // Backup a copy of host window members we will modify
2770 ImGuiWindow* inner_window = mTable.InnerWindow;
2771 mTable.HostIndentX = inner_window->DC.Indent.x;
2772 mTable.HostClipRect = inner_window->ClipRect;
2773 mTable.HostSkipItems = inner_window->SkipItems;
2774 mTable.HostBackupWorkRect = inner_window->WorkRect;
2775 mTable.HostBackupParentWorkRect = inner_window->ParentWorkRect;
2776 mTable.HostBackupColumnsOffset = outer_window->DC.ColumnsOffset;
2777 mTable.HostBackupPrevLineSize = inner_window->DC.PrevLineSize;
2778 mTable.HostBackupCurrLineSize = inner_window->DC.CurrLineSize;
2779 mTable.HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos;
2780 mTable.HostBackupItemWidth = outer_window->DC.ItemWidth;
2781 mTable.HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size;
2782 inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
2783
2784 // Padding and Spacing
2785 // - None ........Content..... Pad .....Content........
2786 // - PadOuter | Pad ..Content..... Pad .....Content.. Pad |
2787 // - PadInner ........Content.. Pad | Pad ..Content........
2788 // - PadOuter+PadInner | Pad ..Content.. Pad | Pad ..Content.. Pad |
2789 const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true
2790 : (flags & ImGuiTableFlags_BordersOuterV) != 0;
2791 const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true;
2792 const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f;
2793 const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f;
2794 const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f;
2795 mTable.CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border;
2796 mTable.CellSpacingX2 = inner_spacing_explicit;
2797 mTable.CellPaddingX = inner_padding_explicit;
2798 mTable.CellPaddingY = g.Style.CellPadding.y;
2799
2800 const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
2801 const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f;
2802 mTable.OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - mTable.CellPaddingX;
2803
2804 mTable.CurrentColumn = -1;
2805 mTable.CurrentRow = -1;
2806 mTable.RowBgColorCounter = 0;
2807 mTable.LastRowFlags = ImGuiTableRowFlags_None;
2808 mTable.InnerClipRect = (inner_window == outer_window) ? mTable.WorkRect : inner_window->ClipRect;
2809 mTable.InnerClipRect.ClipWith(mTable.WorkRect); // We need this to honor inner_width
2810 mTable.InnerClipRect.ClipWithFull(mTable.HostClipRect);
2811 mTable.InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(mTable.InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : inner_window->ClipRect.Max.y;
2812
2813 mTable.RowPosY1 = mTable.RowPosY2 = mTable.WorkRect.Min.y; // This is needed somehow
2814 mTable.RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow()
2815 mTable.FreezeRowsRequest = mTable.FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any
2816 mTable.FreezeColumnsRequest = mTable.FreezeColumnsCount = 0;
2817 mTable.IsUnfrozenRows = true;
2818 mTable.DeclColumnsCount = 0;
2819
2820 // Using opaque colors facilitate overlapping elements of the grid
2821 mTable.BorderColorStrong = ImGui::GetColorU32(ImGuiCol_TableBorderStrong);
2822 mTable.BorderColorLight = ImGui::GetColorU32(ImGuiCol_TableBorderLight);
2823
2824 // Make table current
2825 // const int table_idx = g.Tables.GetIndex(mTable);
2826 // g.CurrentTableStack.push_back(ImGuiPtrOrIndex(table_idx));
2827 // g.CurrentTable = &mTable;
2828 // outer_window->DC.CurrentTableIdx = table_idx;
2829 // if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly.
2830 // inner_window->DC.CurrentTableIdx = table_idx;
2831
2832 if ((table_last_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0)
2833 mTable.IsResetDisplayOrderRequest = true;
2834
2835 // Mark as used
2836 // if (table_idx >= g.TablesLastTimeActive.Size)
2837 // g.TablesLastTimeActive.resize(table_idx + 1, -1.0f);
2838 // g.TablesLastTimeActive[table_idx] = (float)g.Time;
2839 mTable.MemoryCompacted = false;
2840
2841 // Setup memory buffer (clear data if columns count changed)
2842 const int stored_size = mTable.Columns.size();
2843 if (stored_size != 0 && stored_size != columns_count) {
2844 IM_FREE(mTable.RawData);
2845 mTable.RawData = NULL;
2846 }
2847 if (mTable.RawData == NULL) {
2848 BeginInitMemory(columns_count);
2849 mTable.IsInitializing = mTable.IsSettingsRequestLoad = true;
2850 }
2851 if (mTable.IsResetAllRequest)
2852 ResetSettings();
2853 if (mTable.IsInitializing) {
2854 // Initialize
2855 mTable.SettingsOffset = -1;
2856 mTable.IsSortSpecsDirty = true;
2857 mTable.InstanceInteracted = -1;
2858 mTable.ContextPopupColumn = -1;
2859 mTable.ReorderColumn = mTable.ResizedColumn = mTable.LastResizedColumn = -1;
2860 mTable.AutoFitSingleColumn = -1;
2861 mTable.HoveredColumnBody = mTable.HoveredColumnBorder = -1;
2862 for (int n = 0; n < columns_count; n++) {
2863 TableColumn* column = &mTable.Columns[n];
2864 float width_auto = column->WidthAuto;
2865 *column = TableColumn();
2866 column->WidthAuto = width_auto;
2867 column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker
2868 column->DisplayOrder = mTable.DisplayOrderToIndex[n] = (TableColumnIdx) n;
2869 column->IsEnabled = column->IsEnabledNextFrame = true;
2870 }
2871 }
2872
2873 // Load settings
2874 if (mTable.IsSettingsRequestLoad)
2875 LoadSettingsCustom();
2876
2877 if (getCustomColumnsActive()) {
2878 bool expected = true;
2879 if (mSpecificColumnsUpdate.compare_exchange_strong(expected, false)) {
2880 if (mSpecificColumnsActive) {
2881 ApplySpecificColumnSetup();
2882 } else {
2883 LoadSettingsCustom();
2884 }
2885 }
2886 } else if (mSpecificColumnsActive) {
2887 LoadSettingsCustom();
2888 mSpecificColumnsActive = false;
2889 }
2890
2891 // Handle DPI/font resize
2892 // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well.
2893 // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition.
2894 // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory.
2895 // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path.
2896 const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ?
2897 if (mTable.RefScale != 0.0f && mTable.RefScale != new_ref_scale_unit) {
2898 const float scale_factor = new_ref_scale_unit / mTable.RefScale;
2899 //IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", mTable.ID, mTable.RefScaleUnit, new_ref_scale_unit, scale_factor);
2900 for (int n = 0; n < columns_count; n++)
2901 mTable.Columns[n].WidthRequest = mTable.Columns[n].WidthRequest * scale_factor;
2902 }
2903 mTable.RefScale = new_ref_scale_unit;
2904
2905 // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call..
2906 // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user.
2907 // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option.
2908 inner_window->SkipItems = true;
2909
2910 // Clear names
2911 // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn()
2912 if (mTable.ColumnsNames.Buf.Size > 0)
2913 mTable.ColumnsNames.Buf.resize(0);
2914
2915 // Apply queued resizing/reordering/hiding requests
2916 BeginApplyRequests();
2917
2918 return true;
2919 }
2920
2921 template<size_t MaxColumnCount>
2924 ImGuiContext& g = *GImGui;
2925
2926 // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some
2927 // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border)
2928 //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?");
2929
2930 // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our
2931 // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc.
2932 if (!mTable.IsLayoutLocked)
2933 UpdateLayout();
2934
2935 const ImGuiTableFlags flags = mTable.Flags;
2936 ImGuiWindow* inner_window = mTable.InnerWindow;
2937 ImGuiWindow* outer_window = mTable.OuterWindow;
2938 IM_ASSERT(inner_window == g.CurrentWindow);
2939 IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow);
2940
2941 if (mTable.IsInsideRow)
2942 EndRow();
2943
2944 // Context menu in columns body
2945 // if (flags & ImGuiTableFlags_ContextMenuInBody)
2946 // if (mTable.HoveredColumnBody != -1 && !IsAnyItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))
2947 // OpenContextMenu((int)mTable.HoveredColumnBody);
2948
2949 // Finalize table height
2950 inner_window->DC.PrevLineSize = mTable.HostBackupPrevLineSize;
2951 inner_window->DC.CurrLineSize = mTable.HostBackupCurrLineSize;
2952 inner_window->DC.CursorMaxPos = mTable.HostBackupCursorMaxPos;
2953 const float inner_content_max_y = mTable.RowPosY2;
2954 IM_ASSERT(mTable.RowPosY2 == inner_window->DC.CursorPos.y);
2955 if (inner_window != outer_window)
2956 inner_window->DC.CursorMaxPos.y = inner_content_max_y;
2957 else if (!(flags & ImGuiTableFlags_NoHostExtendY))
2958 mTable.OuterRect.Max.y = mTable.InnerRect.Max.y = ImMax(mTable.OuterRect.Max.y, inner_content_max_y); // Patch OuterRect/InnerRect height
2959 mTable.WorkRect.Max.y = ImMax(mTable.WorkRect.Max.y, mTable.OuterRect.Max.y);
2960 mTable.LastOuterHeight = mTable.OuterRect.GetHeight();
2961
2962 // Setup inner scrolling range
2963 // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y,
2964 // but since the later is likely to be impossible to do we'd rather update both axises together.
2965 if (mTable.Flags & ImGuiTableFlags_ScrollX) {
2966 const float outer_padding_for_border = (mTable.Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;
2967 float max_pos_x = mTable.InnerWindow->DC.CursorMaxPos.x;
2968 if (mTable.RightMostEnabledColumn != -1)
2969 max_pos_x = ImMax(max_pos_x, mTable.Columns[mTable.RightMostEnabledColumn].WorkMaxX + mTable.CellPaddingX + mTable.OuterPaddingX - outer_padding_for_border);
2970 if (mTable.ResizedColumn != -1)
2971 max_pos_x = ImMax(max_pos_x, mTable.ResizeLockMinContentsX2);
2972 mTable.InnerWindow->DC.CursorMaxPos.x = max_pos_x;
2973 }
2974
2975 // Pop clipping rect
2976 if (!(flags & ImGuiTableFlags_NoClip))
2977 inner_window->DrawList->PopClipRect();
2978 inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back();
2979
2980 // Draw borders
2981 if ((flags & ImGuiTableFlags_Borders) != 0)
2982 DrawBorders();
2983
2984 // Flatten channels and merge draw calls
2985 mTable.DrawSplitter.SetCurrentChannel(inner_window->DrawList, 0);
2986 if ((mTable.Flags & ImGuiTableFlags_NoClip) == 0)
2987 MergeDrawChannels();
2988 mTable.DrawSplitter.Merge(inner_window->DrawList);
2989
2990 // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable()
2991 const float width_spacings = (mTable.OuterPaddingX * 2.0f) + (mTable.CellSpacingX1 + mTable.CellSpacingX2) * (mTable.ColumnsEnabledCount - 1);
2992 mTable.ColumnsAutoFitWidth = width_spacings + (mTable.CellPaddingX * 2.0f) * mTable.ColumnsEnabledCount;
2993 for (int column_n = 0; column_n < mTable.ColumnsCount; column_n++)
2994 if (mTable.EnabledMaskByIndex.test(column_n)) {
2995 TableColumn* column = &mTable.Columns[column_n];
2996 if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize))
2997 mTable.ColumnsAutoFitWidth += column->WidthRequest;
2998 else
2999 mTable.ColumnsAutoFitWidth += GetColumnWidthAuto(column);
3000 }
3001
3002 // Update scroll
3003 if ((mTable.Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window) {
3004 inner_window->Scroll.x = 0.0f;
3005 } else if (mTable.LastResizedColumn != -1 && mTable.ResizedColumn == -1 && inner_window->ScrollbarX && mTable.InstanceInteracted == mTable.InstanceCurrent) {
3006 // When releasing a column being resized, scroll to keep the resulting column in sight
3007 const float neighbor_width_to_keep_visible = mTable.MinColumnWidth + mTable.CellPaddingX * 2.0f;
3008 TableColumn* column = &mTable.Columns[mTable.LastResizedColumn];
3009 if (column->MaxX < mTable.InnerClipRect.Min.x)
3010 ImGui::SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f);
3011 else if (column->MaxX > mTable.InnerClipRect.Max.x)
3012 ImGui::SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f);
3013 }
3014
3015 // Apply resizing/dragging at the end of the frame
3016 if (mTable.ResizedColumn != -1 && mTable.InstanceCurrent == mTable.InstanceInteracted) {
3017 TableColumn* column = &mTable.Columns[mTable.ResizedColumn];
3018 const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS);
3019 const float new_width = ImFloor(new_x2 - column->MinX - mTable.CellSpacingX1 - mTable.CellPaddingX * 2.0f);
3020 mTable.ResizedColumnNextWidth = new_width;
3021 }
3022
3023 // Pop from id stack
3024 IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == mTable.ID + mTable.InstanceCurrent, "Mismatching PushID/PopID!");
3025 IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= mTable.HostBackupItemWidthStackSize, "Too many PopItemWidth!");
3026 ImGui::PopID();
3027
3028 // Restore window data that we modified
3029 const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos;
3030 inner_window->WorkRect = mTable.HostBackupWorkRect;
3031 inner_window->ParentWorkRect = mTable.HostBackupParentWorkRect;
3032 inner_window->SkipItems = mTable.HostSkipItems;
3033 outer_window->DC.CursorPos = mTable.OuterRect.Min;
3034 outer_window->DC.ItemWidth = mTable.HostBackupItemWidth;
3035 outer_window->DC.ItemWidthStack.Size = mTable.HostBackupItemWidthStackSize;
3036 outer_window->DC.ColumnsOffset = mTable.HostBackupColumnsOffset;
3037
3038 // Layout in outer window
3039 // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding
3040 // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414)
3041 if (inner_window != outer_window) {
3042 ImGui::EndChild();
3043 } else {
3044 ImGui::ItemSize(mTable.OuterRect.GetSize());
3045 ImGui::ItemAdd(mTable.OuterRect, 0);
3046 }
3047
3048 // Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar
3049 if (mTable.Flags & ImGuiTableFlags_NoHostExtendX) {
3050 // FIXME-TABLE: Could we remove this section?
3051 // ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents
3052 IM_ASSERT((mTable.Flags & ImGuiTableFlags_ScrollX) == 0);
3053 outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, mTable.OuterRect.Min.x + mTable.ColumnsAutoFitWidth);
3054 } else if (mTable.UserOuterSize.x <= 0.0f) {
3055 const float decoration_size = (mTable.Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f;
3056 outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, mTable.OuterRect.Min.x + mTable.ColumnsAutoFitWidth + decoration_size - mTable.UserOuterSize.x);
3057 outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(mTable.OuterRect.Max.x, mTable.OuterRect.Min.x + mTable.ColumnsAutoFitWidth));
3058 } else {
3059 outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, mTable.OuterRect.Max.x);
3060 }
3061 if (mTable.UserOuterSize.y <= 0.0f) {
3062 const float decoration_size = (mTable.Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f;
3063 outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - mTable.UserOuterSize.y);
3064 outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(mTable.OuterRect.Max.y, inner_content_max_y));
3065 } else {
3066 // OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set)
3067 outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, mTable.OuterRect.Max.y);
3068 }
3069
3070 // Save settings
3071 if (mTable.IsSettingsDirty)
3072 SaveSettingsCustom();
3073 mTable.IsInitializing = false;
3074
3075 // Clear or restore current table, if any
3076 // IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == mTable);
3077 // g.CurrentTableStack.pop_back();
3078 // g.CurrentTable = g.CurrentTableStack.Size ? g.Tables.GetByIndex(g.CurrentTableStack.back().Index) : NULL;
3079 // outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1;
3080 }
3081
3082 template<size_t MaxColumnCount>
3085 TableColumn& column = mTable.Columns[TableColumnIdx];
3086 const char* columnName = GetColumnName(TableColumnIdx);
3087 // Make sure we can't hide the last active column
3088 bool menu_item_active = (column.Flags & ImGuiTableColumnFlags_NoHide) ? false : true;
3089 if (column.IsEnabled && mTable.ColumnsEnabledCount <= 1)
3090 menu_item_active = false;
3091 if (ImGui::MenuItem(columnName, NULL, column.IsEnabled, menu_item_active && !mSpecificColumnsActive))
3092 column.IsEnabledNextFrame = !column.IsEnabled;
3093
3094 if (ImGui::IsItemHovered()) {
3095 ImGui::SetTooltip("%s", mColumns.at(TableColumnIdx).Popup().c_str());
3096 }
3097 }
3098
3099 template<size_t MaxColumnCount>
3102 if (mTable.IsLayoutLocked == false && column_n >= mTable.DeclColumnsCount)
3103 return ""; // NameOffset is invalid at this point
3104 const TableColumn* column = &mTable.Columns[column_n];
3105 if (column->NameOffset == -1)
3106 return "";
3107 return &mTable.ColumnsNames.Buf[column->NameOffset];
3108 }
3109
3110 template<size_t MaxColumnCount>
3113 IM_ASSERT(mTable.IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!");
3114 IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS);
3115 IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit
3116
3117 mTable.FreezeColumnsRequest = (mTable.Flags & ImGuiTableFlags_ScrollX) ? (TableColumnIdx) columns : 0;
3118 mTable.FreezeColumnsCount = (mTable.InnerWindow->Scroll.x != 0.0f) ? mTable.FreezeColumnsRequest : 0;
3119 mTable.FreezeRowsRequest = (mTable.Flags & ImGuiTableFlags_ScrollY) ? (TableColumnIdx) rows : 0;
3120 mTable.FreezeRowsCount = (mTable.InnerWindow->Scroll.y != 0.0f) ? mTable.FreezeRowsRequest : 0;
3121 mTable.IsUnfrozenRows = (mTable.FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b
3122 }
3123
3124 template<size_t MaxColumnCount>
3127 const TableColumn* column = &mTable.Columns[column_n];
3128 float x1 = column->MinX;
3129 float x2 = column->MaxX;
3130 if (column->PrevEnabledColumn == -1)
3131 x1 -= mTable.CellSpacingX1;
3132 if (column->NextEnabledColumn == -1)
3133 x2 += mTable.CellSpacingX2;
3134 return ImRect(x1, mTable.RowPosY1, x2, mTable.RowPosY2);
3135 }
3136
3137 template<size_t MaxColumnCount>
3140 if (!(mTable.Flags & ImGuiTableFlags_Sortable))
3141 return NULL;
3142
3143 // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths.
3144 if (!mTable.IsLayoutLocked)
3145 UpdateLayout();
3146
3147 if (mTable.IsSortSpecsDirty)
3148 SortSpecsBuild();
3149
3150 return &mTable.SortSpecs;
3151 }
3152
3153 template<size_t MaxColumnCount>
3155 void MainTable<MaxColumnCount>::SetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) {
3156 IM_ASSERT(mTable.IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!");
3157 IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()");
3158 if (mTable.DeclColumnsCount >= mTable.ColumnsCount) {
3159 IM_ASSERT_USER_ERROR(mTable.DeclColumnsCount < mTable.ColumnsCount, "Called TableSetupColumn() too many times!");
3160 return;
3161 }
3162
3163 TableColumn* column = &mTable.Columns[mTable.DeclColumnsCount];
3164 mTable.DeclColumnsCount++;
3165
3166 // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa.
3167 // Give a grace to users of ImGuiTableFlags_ScrollX.
3168 if (mTable.IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0)
3169 IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitely in either Table or Column.");
3170
3171 // When passing a width automatically enforce WidthFixed policy
3172 // (whereas TableSetupColumnFlags would default to WidthAuto if mTable.s not Resizable)
3173 if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f)
3174 if ((mTable.Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (mTable.Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)
3175 flags |= ImGuiTableColumnFlags_WidthFixed;
3176
3177 TableSetupColumnFlags(column, flags);
3178
3179 column->UserID = user_id;
3180 flags = column->Flags;
3181
3182 // Initialize defaults
3183 column->InitStretchWeightOrWidth = init_width_or_weight;
3184 if (mTable.IsInitializing) {
3185 // Init width or weight
3186 if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) {
3187 if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f)
3188 column->WidthRequest = init_width_or_weight;
3189 if (flags & ImGuiTableColumnFlags_WidthStretch)
3190 column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f;
3191
3192 // Disable auto-fit if an explicit width/weight has been specified
3193 if (init_width_or_weight > 0.0f)
3194 column->AutoFitQueue = 0x00;
3195 }
3196
3197 // Init default visibility/sort state
3198 if ((flags & ImGuiTableColumnFlags_DefaultHide) && (mTable.SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0)
3199 column->IsEnabled = column->IsEnabledNextFrame = false;
3200 if (flags & ImGuiTableColumnFlags_DefaultSort && (mTable.SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) {
3201 column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs.
3202 column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8) ImGuiSortDirection_Descending : (ImU8) (ImGuiSortDirection_Ascending);
3203 }
3204 }
3205
3206 // Store name (append with zero-terminator in contiguous buffer)
3207 column->NameOffset = -1;
3208 if (label != NULL && label[0] != 0) {
3209 column->NameOffset = (ImS16) mTable.ColumnsNames.size();
3210 mTable.ColumnsNames.append(label, label + strlen(label) + 1);
3211 }
3212 }
3213
3214 template<size_t MaxColumnCount>
3216 void MainTable<MaxColumnCount>::ColumnHeader(const char* label, bool show_label, ImTextureID texture, Alignment alignment, const char* popupText) {
3217 // TODO change eventually (to line height or something)
3218 const float image_size = 16.f;
3219
3220 // Show label if texture is null
3221 if (!texture || getShowHeaderAsText()) {
3222 show_label = true;
3223 }
3224
3225 ImGuiContext& g = *GImGui;
3226 ImGuiWindow* window = g.CurrentWindow;
3227 if (window->SkipItems)
3228 return;
3229
3230 IM_ASSERT(mTable.CurrentColumn != -1);
3231 const int column_n = mTable.CurrentColumn;
3232 TableColumn* column = &mTable.Columns[column_n];
3233
3234 // Label
3235 if (label == NULL)
3236 label = "";
3237 const char* label_end = ImGui::FindRenderedTextEnd(label);
3238 ImVec2 label_size = ImGui::CalcTextSize(label, label_end, true);
3239 ImVec2 label_pos = window->DC.CursorPos;
3240
3241 // If we already got a row height, there's use that.
3242 // FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect?
3243 ImRect cell_r = GetCellBgRect(column_n);
3244 float label_height = mTable.RowMinHeight - mTable.CellPaddingY * 2.0f;
3245 if (show_label) {
3246 label_height = ImMax(label_size.y, label_height);
3247 } else {
3248 label_height = ImMax(image_size, label_height);
3249 }
3250
3251 // Calculate ideal size for sort order arrow
3252 float w_arrow = 0.0f;
3253 float w_sort_text = 0.0f;
3254 char sort_order_suf[4] = "";
3255 const float ARROW_SCALE = 0.65f;
3256 if ((mTable.Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) {
3257 if (column->SortOrder > -1) {
3258 w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);
3259 if (column->SortOrder > 0) {
3260 ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1);
3261 w_sort_text = g.Style.ItemInnerSpacing.x + ImGui::CalcTextSize(sort_order_suf).x;
3262 }
3263 } else {
3264 w_arrow = g.Style.ItemInnerSpacing.x;
3265 }
3266 }
3267
3268 // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considering for merging.
3269 float max_pos_x = label_pos.x + w_sort_text + w_arrow;
3270 if (show_label) {
3271 max_pos_x += label_size.x;
3272 } else {
3273 max_pos_x += image_size;
3274 }
3275 column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, column->WorkMaxX);
3276 column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x);
3277
3278 // Keep header highlighted when context menu is open.
3279 const bool selected = (mTable.IsContextPopupOpen && mTable.ContextPopupColumn == column_n && mTable.InstanceInteracted == mTable.InstanceCurrent);
3280 ImGuiID id = window->GetID(label);
3281 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));
3282 ImGui::ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal
3283 if (!ImGui::ItemAdd(bb, id))
3284 return;
3285
3286 //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
3287 //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]
3288
3289 // Using AllowItemOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items.
3290 bool hovered, held;
3291 bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_AllowItemOverlap);
3292 if (g.ActiveId != id)
3293 ImGui::SetItemAllowOverlap();
3294 if (held || hovered || selected) {
3295 const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered
3296 : ImGuiCol_Header);
3297 //RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
3298 SetBgColor(ImGuiTableBgTarget_CellBg, col, mTable.CurrentColumn);
3299 ImGui::RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
3300 } else {
3301 // Submit single cell bg color in the case we didn't submit a full header row
3302 if ((mTable.RowFlags & ImGuiTableRowFlags_Headers) == 0)
3303 SetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImGuiCol_TableHeaderBg), mTable.CurrentColumn);
3304 }
3305 if (held)
3306 mTable.HeldHeaderColumn = (TableColumnIdx) column_n;
3307 window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f;
3308
3309 // Drag and drop to re-order columns.
3310 // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone.
3311 if (held && (mTable.Flags & ImGuiTableFlags_Reorderable) && ImGui::IsMouseDragging(0) && !g.DragDropActive) {
3312 // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x
3313 mTable.ReorderColumn = (TableColumnIdx) column_n;
3314 mTable.InstanceInteracted = mTable.InstanceCurrent;
3315
3316 // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder.
3317 if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x)
3318 if (TableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &mTable.Columns[column->PrevEnabledColumn] : NULL)
3319 if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder))
3320 if ((column->IndexWithinEnabledSet < mTable.FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < mTable.FreezeColumnsRequest))
3321 mTable.ReorderColumnDir = -1;
3322 if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x)
3323 if (TableColumn* next_column = (column->NextEnabledColumn != -1) ? &mTable.Columns[column->NextEnabledColumn] : NULL)
3324 if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder))
3325 if ((column->IndexWithinEnabledSet < mTable.FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < mTable.FreezeColumnsRequest))
3326 mTable.ReorderColumnDir = +1;
3327 }
3328
3329 // Sort order arrow
3330 const float ellipsis_max = cell_r.Max.x - w_arrow - w_sort_text;
3331 if ((mTable.Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) {
3332 if (column->SortOrder != -1) {
3333 float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text);
3334 float y = label_pos.y;
3335 if (column->SortOrder > 0) {
3336 ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_Text, 0.70f));
3337 ImGui::RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf);
3338 ImGui::PopStyleColor();
3339 x += w_sort_text;
3340 }
3341 ImGui::RenderArrow(window->DrawList, ImVec2(x, y), ImGui::GetColorU32(ImGuiCol_Text), column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE);
3342 }
3343
3344 // Handle clicking on column header to adjust Sort Order
3345 if (pressed && mTable.ReorderColumn != column_n) {
3346 ImGuiSortDirection sort_direction = GetColumnNextSortDirection(column);
3347 SetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift);
3348 }
3349 }
3350
3351 // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will
3352 // be merged into a single draw call.
3353 //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE);
3354 if (show_label) {
3355 // ImGui::RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max,
3356 // ellipsis_max, label, label_end, &label_size);
3357
3358 float newX = label_pos.x;
3359
3360 switch (alignment) {
3361 case Alignment::Center:
3362 newX = ellipsis_max - ((ellipsis_max - label_pos.x) / 2) - (label_size.x / 2);
3363
3364 if (newX <= label_pos.x) newX = label_pos.x;
3365 break;
3366 case Alignment::Right:
3367 newX = ellipsis_max - label_size.x;
3368 if (newX <= label_pos.x) newX = label_pos.x;
3369 break;
3370 // Nothing to do
3371 case Alignment::Left:
3372 case Alignment::Unaligned: break;
3373 }
3374
3375 ImGui::RenderTextEllipsis(window->DrawList, ImVec2(newX, label_pos.y), ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size);
3376 } else {
3377 float newX = label_pos.x;
3378
3379 switch (alignment) {
3380 case Alignment::Center:
3381 newX = ellipsis_max - ((ellipsis_max - label_pos.x) / 2) - (image_size / 2);
3382 if (newX <= label_pos.x) newX = label_pos.x;
3383 break;
3384 case Alignment::Right:
3385 newX = ellipsis_max - image_size;
3386 // ImGui::SetCursorPosX(cursorPosX + textSpace - contentSize.x);
3387 if (newX <= label_pos.x) newX = label_pos.x;
3388 break;
3389 // Nothing to do
3390 case Alignment::Left:
3391 case Alignment::Unaligned: break;
3392 }
3393
3394 ImRect ibb(ImVec2(newX, label_pos.y), ImVec2(newX, label_pos.y) + image_size);
3395
3396 window->DrawList->AddImage(texture, ibb.Min, ibb.Max);
3397 }
3398
3399 // const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x);
3400 // if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay)
3401 // ImGui::SetTooltip("%.*s", (int)(label_end - label), label);
3402 if (ImGui::IsItemHovered()) {
3403 ImGui::SetTooltip("%s", popupText);
3404 }
3405
3406 // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden
3407 // if (ImGui::IsMouseReleased(1) && ImGui::IsItemHovered())
3408 // ImGui::TableOpenContextMenu(column_n);
3409 }
3410
3411 template<size_t MaxColumnCount>
3414 const float posX = ImGui::GetCursorPosX();
3415 float newX = posX;
3416 float textWidth = ImGui::CalcTextSize(text).x;
3417 ImGuiWindow* window = ImGui::GetCurrentWindowRead();
3418 float columnWidth = window->WorkRect.Max.x - window->DC.CursorPos.x;
3419
3420 switch (getAlignment()) {
3421 case Alignment::Left:
3422 break;
3423 case Alignment::Center:
3424 newX = posX + columnWidth / 2 - textWidth / 2;
3425 break;
3426 case Alignment::Right:
3427 newX = posX + columnWidth - textWidth;
3428 break;
3429 // Nothing to do
3430 case Alignment::Unaligned: break;
3431 }
3432
3433 // Clip to left, if text is bigger than current column
3434 if (newX < posX) {
3435 newX = posX;
3436 }
3437
3438 ImGui::SetCursorPosX(newX);
3439
3440 ImGui::TextUnformatted(text);
3441
3442 // Set cursorMaxPos manually, so right aligned elements also shrink the column
3443 window->DC.CursorMaxPos.x = window->Pos.x + posX + textWidth;
3444 }
3445
3446 template<size_t MaxColumnCount>
3448 void MainTable<MaxColumnCount>::AlignedTextColumn(const std::string& text) {
3449 AlignedTextColumn(text.c_str());
3450 }
3451
3452 template<size_t MaxColumnCount>
3454 template<typename... Args>
3455 void MainTable<MaxColumnCount>::AlignedTextColumn(std::string_view format, Args&&... args) {
3456 AlignedTextColumn(std::vformat(format, std::make_format_args(args...)));
3457 }
3458
3459 template<size_t MaxColumnCount>
3461 bool MainTable<MaxColumnCount>::SpinnerAligned(const char* label, float radius, float thickness, const ImU32& color) {
3462 const float posX = ImGui::GetCursorPosX();
3463 float newX = posX;
3464 float elementWidth = radius * 2 + thickness * 2;
3465 ImGuiWindow* window = ImGui::GetCurrentWindowRead();
3466 float columnWidth = window->WorkRect.Max.x - window->DC.CursorPos.x;
3467
3468 switch (getAlignment()) {
3469 case Alignment::Left:
3470 break;
3471 case Alignment::Center:
3472 newX = posX + columnWidth / 2 - elementWidth / 2;
3473 break;
3474 case Alignment::Right:
3475 newX = posX + columnWidth - elementWidth;
3476 break;
3477 }
3478
3479 // Clip to left, if text is bigger than current column
3480 if (newX < posX) {
3481 newX = posX;
3482 }
3483
3484 ImGui::SetCursorPosX(newX);
3485
3486 return ImGuiEx::Spinner(label, radius, thickness, color);
3487 }
3488
3489 template<size_t MaxColumnCount>
3492 for (int i = getCustomColumnsFirstColumn(); i < mTable.Columns.size(); ++i) {
3493 auto& column = mTable.Columns[i];
3494 bool enabled = std::ranges::find_if(mSpecificColumnCache, [i, this](const size_t& pVal) -> bool { return mColumns.at(i).UserId == pVal; }) != mSpecificColumnCache.end();
3495 column.IsEnabledNextFrame = enabled;
3496 }
3497 }
3498} // namespace ArcdpsExtension
3499
3500#pragma warning(pop)
Alignment
Definition arcdps_structs.h:64
static const std::string & STranslate(T pId)
Definition Localization.h:43
Definition MainTable.h:129
std::bitset< MaxColumnCount > ColumnBitMask
Definition MainTable.h:131
virtual void MigrateSettings()
Definition MainTable.h:250
virtual ~MainTable()=default
void ResetSpecificColumnSetup()
Definition MainTable.h:862
void Draw()
Definition MainTable.h:734
int GetColumnIndex()
Definition MainTable.h:2673
virtual bool getCustomColumnsFeatureActive()
Definition MainTable.h:240
virtual Alignment & getAlignment()=0
static size_t GetMaxColumnCount()
Definition MainTable.h:145
MainTable(MainTable &&pOther) noexcept=delete
ImRect GetCellBgRect(int column_n)
Definition MainTable.h:3126
virtual Alignment & getHeaderAlignment()=0
void AlignedTextColumn(const char *text)
Definition MainTable.h:3413
bool IsCurrentRowHovered()
Definition MainTable.h:2667
bool GetSpecificColumnsActive() const
Definition MainTable.h:150
virtual bool getMaxHeightActive()
Definition MainTable.h:227
void EndMaxHeightRow()
Definition MainTable.h:926
Table mTable
Definition MainTable.h:529
virtual void DrawStyleSubMenu()
Definition MainTable.h:869
void NextRow(ImGuiTableRowFlags row_flags=0, float min_row_height=0.0f)
Definition MainTable.h:886
virtual bool & getShowAlternatingBackground()=0
void MenuItemColumnVisibility(int TableColumnIdx)
Definition MainTable.h:3084
virtual void DrawRows(TableColumnIdx pFirstColumnIndex)=0
virtual void Sort(const ImGuiTableColumnSortSpecs *mColumnSortSpecs)=0
MainTable(const MainTable &pOther)=delete
virtual bool getShowScrollbar()
Definition MainTable.h:225
MainTable(const std::vector< MainTableColumn > &pColumns, MainWindow *pMainWindow, MainTableFlags pFlags=0)
Definition MainTable.h:690
void SetupScrollFreeze(int columns, int rows)
Definition MainTable.h:3112
void ColumnHeader(const char *label, bool show_label, ImTextureID texture, Alignment alignment, const char *popupText)
Definition MainTable.h:3216
virtual bool & getHighlightHoveredRows()=0
bool Begin(const char *str_id, int columns_count, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, ImGuiWindowFlags child_window_flags)
Definition MainTable.h:2679
ImGuiTableSortSpecs * GetSortSpecs()
Definition MainTable.h:3139
void ApplySpecificColumnSetup()
Definition MainTable.h:3491
virtual bool & getCustomColumnsActive()
Definition MainTable.h:241
virtual std::string getTableId()=0
void RequestSort()
Definition MainTable.h:146
void SetupColumn(const char *label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id)
Definition MainTable.h:3155
virtual int getCustomColumnsFirstColumn()
Definition MainTable.h:243
virtual const char * getCategoryName(const std::string &pCat)=0
bool IsCurrentColumnHovered()
Definition MainTable.h:2656
void DrawColumnSetupMenu()
Definition MainTable.h:828
const char * GetColumnName(int column_n)
Definition MainTable.h:3101
MainTable & operator=(MainTable &&pOther) noexcept=delete
bool NextColumn()
Definition MainTable.h:908
MainTable & operator=(const MainTable &pOther)=delete
virtual void DrawColumnSetupSubMenu()
Definition MainTable.h:838
bool SpinnerAligned(const char *label, float radius, float thickness, const ImU32 &color)
Definition MainTable.h:3461
void SetSpecificColumnSetup(const std::vector< size_t > &pNewColumns)
Definition MainTable.h:853
virtual TableSettings & getTableSettings()=0
virtual bool & getShowHeaderAsText()=0
virtual int & getMaxDisplayed()=0
void End()
Definition MainTable.h:2923
Definition MainWindow.h:22
float GetMaxCursorPos()
Definition MainWindow.cpp:164
virtual bool & GetShowScrollbar()=0
virtual void SetMaxHeightCursorPos(float pNewCursorPos=ImGui::GetCursorPosY())
Definition MainWindow.cpp:136
void RegisterDrawStyleSubMenuHook(DrawStyleSubMenuHookFunction pFun)
Definition MainWindow.cpp:160
Definition ArcdpsExtension.h:10
MainTableFlags_
Definition MainTable.h:90
@ MainTableFlags_None
Definition MainTable.h:91
@ MainTableFlags_SubWindow
Definition MainTable.h:92
@ ET_ShowBasedOnMap
Definition ExtensionTranslations.h:49
@ ET_HighlightHoveredRow
Definition ExtensionTranslations.h:51
@ ET_ColumnSetup
Definition ExtensionTranslations.h:48
@ ET_AlternatingRowBg
Definition ExtensionTranslations.h:50
@ ET_HeaderAlignment
Definition ExtensionTranslations.h:53
@ ET_SettingsShowHeaderText
Definition ExtensionTranslations.h:56
@ ET_MaxDisplayed
Definition ExtensionTranslations.h:52
@ ET_ColumnAlignment
Definition ExtensionTranslations.h:54
int MainTableFlags
Definition MainTable.h:94
ImS16 TableColumnIdx
Definition MainTable.h:45
bool EnumCombo(const char *label, E &storage, const R &values, const std::map< E, std::function< const std::string &()> > &pPopupText={})
Definition Widgets.h:64
bool Spinner(const char *label, float radius, float thickness, const ImU32 &color)
Definition Widgets.cpp:19
Definition imgui_stdlib.h:16
Definition MainTable.h:55
MainTableColumn(ImU32 pUserId, const std::function< std::string()> &pName, const std::function< void *()> &pTexture, std::string pCategory, const std::function< std::string()> &pPopup, bool pDefaultVisibility=true)
Definition MainTable.h:76
ImU32 UserId
Definition MainTable.h:56
std::function< void *()> Texture
Definition MainTable.h:58
MainTableColumn(E pUserId, const std::function< std::string()> &pName, const std::function< void *()> &pTexture, std::string pCategory, const std::function< std::string()> &pPopup, bool pDefaultVisibility=true)
Definition MainTable.h:86
MainTableColumn(ImU32 pUserId, const std::function< std::string()> &pName, const std::function< void *()> &pTexture, std::string pCategory, bool pDefaultVisibility=true)
Definition MainTable.h:63
std::function< std::string()> Name
Definition MainTable.h:57
bool DefaultVisibility
Definition MainTable.h:60
std::function< std::string()> Popup
Definition MainTable.h:61
std::string Category
Definition MainTable.h:59
MainTableColumn(E pUserId, const std::function< std::string()> &pName, const std::function< void *()> &pTexture, std::string pCategory, bool pDefaultVisibility=true)
Definition MainTable.h:73
TableColumnIdx DisplayOrder
Definition MainTable.h:158
ImU8 SortDirection
Definition MainTable.h:160
float WidthOrWeight
Definition MainTable.h:155
std::strong_ordering operator<=>(const TableColumnSettings &pOther) const
Definition MainTable.h:164
std::strong_ordering operator<=>(const ImGuiID &pOther) const
Definition MainTable.h:168
ImU8 IsStretch
Definition MainTable.h:162
ImU8 IsEnabled
Definition MainTable.h:161
friend void from_json(const nlohmann::json &nlohmann_json_j, TableColumnSettings &nlohmann_json_t)
Definition MainTable.h:190
ImGuiID UserID
Definition MainTable.h:156
bool operator==(const ImGuiID &pOther) const
Definition MainTable.h:176
bool operator==(const TableColumnSettings &pOther) const
Definition MainTable.h:172
TableColumnIdx SortOrder
Definition MainTable.h:159
friend void to_json(nlohmann::json &nlohmann_json_j, const TableColumnSettings &nlohmann_json_t)
Definition MainTable.h:180
Definition MainTable.h:354
TableColumnIdx NextEnabledColumn
Definition MainTable.h:376
TableColumnIdx DisplayOrder
Definition MainTable.h:373
TableColumnIdx SortOrder
Definition MainTable.h:377
ImGuiTableDrawChannelIdx DrawChannelUnfrozen
Definition MainTable.h:380
ImU8 CannotSkipItemsQueue
Definition MainTable.h:390
float InitStretchWeightOrWidth
Definition MainTable.h:362
float ItemWidth
Definition MainTable.h:367
ImGuiTableDrawChannelIdx DrawChannelCurrent
Definition MainTable.h:378
float MinX
Definition MainTable.h:357
float ContentMaxXHeadersUsed
Definition MainTable.h:370
bool IsVisibleY
Definition MainTable.h:384
float ContentMaxXUnfrozen
Definition MainTable.h:369
ImU8 SortDirectionsAvailMask
Definition MainTable.h:393
bool IsRequestOutput
Definition MainTable.h:385
TableColumn()
Definition MainTable.h:396
float ContentMaxXFrozen
Definition MainTable.h:368
TableColumnIdx IndexWithinEnabledSet
Definition MainTable.h:374
ImS16 NameOffset
Definition MainTable.h:372
ImS8 NavLayerCurrent
Definition MainTable.h:388
bool IsSkipItems
Definition MainTable.h:386
ImGuiTableDrawChannelIdx DrawChannelFrozen
Definition MainTable.h:379
ImGuiID UserID
Definition MainTable.h:364
TableColumnIdx PrevEnabledColumn
Definition MainTable.h:375
bool IsEnabledNextFrame
Definition MainTable.h:382
ImU8 AutoFitQueue
Definition MainTable.h:389
ImRect ClipRect
Definition MainTable.h:363
float WidthAuto
Definition MainTable.h:360
ImU8 SortDirection
Definition MainTable.h:391
float WidthRequest
Definition MainTable.h:359
float WorkMaxX
Definition MainTable.h:366
ImU8 SortDirectionsAvailList
Definition MainTable.h:394
float StretchWeight
Definition MainTable.h:361
float WorkMinX
Definition MainTable.h:365
bool IsEnabled
Definition MainTable.h:381
float WidthGiven
Definition MainTable.h:356
bool IsVisibleX
Definition MainTable.h:383
float ContentMaxXHeadersIdeal
Definition MainTable.h:371
float MaxX
Definition MainTable.h:358
bool IsPreserveWidthAuto
Definition MainTable.h:387
ImGuiTableColumnFlags Flags
Definition MainTable.h:355
ImU8 SortDirectionsAvailCount
Definition MainTable.h:392
ImU32 Version
Definition MainTable.h:201
ImGuiTableFlags SaveFlags
Definition MainTable.h:202
float RefScale
Definition MainTable.h:203
std::vector< TableColumnSettings > Columns
Definition MainTable.h:204
Definition MainTable.h:409
ImGuiTableSortSpecs SortSpecs
Definition MainTable.h:481
TableColumnIdx DeclColumnsCount
Definition MainTable.h:485
ImVec2 HostBackupCurrLineSize
Definition MainTable.h:469
ImSpan< TableColumn > Columns
Definition MainTable.h:413
TableColumnIdx ResizedColumn
Definition MainTable.h:489
TableColumnIdx FreezeRowsRequest
Definition MainTable.h:498
ImRect InnerRect
Definition MainTable.h:458
float RowPosY1
Definition MainTable.h:429
float CellSpacingX2
Definition MainTable.h:448
ImGuiTextBuffer ColumnsNames
Definition MainTable.h:477
TableColumnIdx RightMostEnabledColumn
Definition MainTable.h:496
TableColumnIdx HeldHeaderColumn
Definition MainTable.h:491
bool IsDefaultSizingPolicy
Definition MainTable.h:518
TableColumnIdx RightMostStretchedColumn
Definition MainTable.h:495
float BorderX1
Definition MainTable.h:440
TableColumnIdx ReorderColumn
Definition MainTable.h:492
ImU32 BorderColorStrong
Definition MainTable.h:438
ImU32 RowBgColor[2]
Definition MainTable.h:437
int HostBackupItemWidthStackSize
Definition MainTable.h:474
float RowTextBaseline
Definition MainTable.h:432
float RowIndentOffsetX
Definition MainTable.h:433
bool IsInsideRow
Definition MainTable.h:507
ImGuiTableFlags Flags
Definition MainTable.h:411
float MinColumnWidth
Definition MainTable.h:443
bool IsUsingHeaders
Definition MainTable.h:510
ImSpan< ImGuiTableCellData > RowCellData
Definition MainTable.h:415
ImGuiTableColumnSortSpecs SortSpecsSingle
Definition MainTable.h:479
ImDrawListSplitter DrawSplitter
Definition MainTable.h:478
ImVector< ImGuiTableColumnSortSpecs > SortSpecsMulti
Definition MainTable.h:480
float LastFirstRowHeight
Definition MainTable.h:450
bool IsContextPopupOpen
Definition MainTable.h:511
bool IsSettingsDirty
Definition MainTable.h:513
TableColumnIdx RowCellDataCurrent
Definition MainTable.h:502
ImRect WorkRect
Definition MainTable.h:459
float ResizeLockMinContentsX2
Definition MainTable.h:455
ImVec1 HostBackupColumnsOffset
Definition MainTable.h:472
IMGUI_API Table()
Definition MainTable.h:522
float InnerWidth
Definition MainTable.h:451
bool HostSkipItems
Definition MainTable.h:520
float RowMinHeight
Definition MainTable.h:431
ImGuiWindow * OuterWindow
Definition MainTable.h:475
float CellSpacingX1
Definition MainTable.h:447
int RowBgColorCounter
Definition MainTable.h:436
ImS16 InstanceCurrent
Definition MainTable.h:427
ImGuiID ID
Definition MainTable.h:410
float CellPaddingX
Definition MainTable.h:445
ImVec2 HostBackupCursorMaxPos
Definition MainTable.h:470
TableColumnIdx ColumnsEnabledCount
Definition MainTable.h:483
TableColumnIdx ColumnsEnabledFixedCount
Definition MainTable.h:484
TableColumnIdx LastResizedColumn
Definition MainTable.h:490
bool MemoryCompacted
Definition MainTable.h:519
ImRect HostBackupWorkRect
Definition MainTable.h:465
bool IsInitializing
Definition MainTable.h:508
float RefScale
Definition MainTable.h:456
TableColumnIdx ReorderColumnDir
Definition MainTable.h:493
ImGuiTableDrawChannelIdx DummyDrawChannel
Definition MainTable.h:503
ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen
Definition MainTable.h:505
ImRect InnerClipRect
Definition MainTable.h:460
ImSpan< TableColumnIdx > DisplayOrderToIndex
Definition MainTable.h:414
float BorderX2
Definition MainTable.h:441
ImGuiTableDrawChannelIdx Bg2DrawChannelCurrent
Definition MainTable.h:504
bool IsResetAllRequest
Definition MainTable.h:515
bool IsLayoutLocked
Definition MainTable.h:506
bool IsSortSpecsDirty
Definition MainTable.h:509
TableColumnIdx HoveredColumnBorder
Definition MainTable.h:487
ImVec2 HostBackupPrevLineSize
Definition MainTable.h:468
bool IsSettingsRequestLoad
Definition MainTable.h:512
ImRect OuterRect
Definition MainTable.h:457
IMGUI_API ~Table()
Definition MainTable.h:526
ImRect HostBackupParentWorkRect
Definition MainTable.h:466
float CellPaddingY
Definition MainTable.h:446
TableColumnIdx FreezeColumnsCount
Definition MainTable.h:501
TableColumnIdx ContextPopupColumn
Definition MainTable.h:497
float ResizedColumnNextWidth
Definition MainTable.h:454
TableColumnIdx HoveredColumnBody
Definition MainTable.h:486
int LastFrameActive
Definition MainTable.h:422
ImGuiWindow * InnerWindow
Definition MainTable.h:476
ImRect Bg2ClipRectForDrawCmd
Definition MainTable.h:463
int CurrentColumn
Definition MainTable.h:426
int CurrentRow
Definition MainTable.h:424
int SettingsOffset
Definition MainTable.h:421
ImRect HostClipRect
Definition MainTable.h:464
ColumnBitMask EnabledMaskByDisplayOrder
Definition MainTable.h:416
ImRect BgClipRect
Definition MainTable.h:461
TableColumnIdx FreezeRowsCount
Definition MainTable.h:499
int ColumnsCount
Definition MainTable.h:423
TableColumnIdx FreezeColumnsRequest
Definition MainTable.h:500
bool IsDefaultDisplayOrder
Definition MainTable.h:514
float HostBackupItemWidth
Definition MainTable.h:473
ImS16 InstanceInteracted
Definition MainTable.h:428
ImVec2 UserOuterSize
Definition MainTable.h:471
TableColumnIdx SortSpecsCount
Definition MainTable.h:482
float ColumnsAutoFitWidth
Definition MainTable.h:453
float RowPosY2
Definition MainTable.h:430
ImGuiTableRowFlags LastRowFlags
Definition MainTable.h:435
TableColumnIdx AutoFitSingleColumn
Definition MainTable.h:488
float HostIndentX
Definition MainTable.h:442
float ColumnsGivenWidth
Definition MainTable.h:452
float OuterPaddingX
Definition MainTable.h:444
ColumnBitMask VisibleMaskByIndex
Definition MainTable.h:418
ColumnBitMask RequestOutputMaskByIndex
Definition MainTable.h:419
TableColumnIdx LeftMostStretchedColumn
Definition MainTable.h:494
ColumnBitMask EnabledMaskByIndex
Definition MainTable.h:417
bool IsResetDisplayOrderRequest
Definition MainTable.h:516
void * RawData
Definition MainTable.h:412
ImGuiTableFlags SettingsLoadedFlags
Definition MainTable.h:420
int CurrentHoveredRow
Definition MainTable.h:425
ImGuiTableRowFlags RowFlags
Definition MainTable.h:434
ImRect Bg0ClipRectForDrawCmd
Definition MainTable.h:462
ImU32 BorderColorLight
Definition MainTable.h:439
ImRect HostBackupInnerClipRect
Definition MainTable.h:467
bool IsUnfrozenRows
Definition MainTable.h:517
float LastOuterHeight
Definition MainTable.h:449