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