Tables: add support for ImGuiTableFlags_TrackTopologyChanges (#9108, #4046)

This commit is contained in:
ocornut
2026-06-08 17:40:05 +02:00
parent b2377d616d
commit 2f94e7dc13
4 changed files with 181 additions and 50 deletions

View File

@@ -5751,8 +5751,11 @@ void ImGui::NewFrame()
if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
TableGcCompactTransientBuffers(g.Tables.GetByIndex(i));
for (ImGuiTableTempData& table_temp_data : g.TablesTempData)
{
table_temp_data.ReconcileColumnsRequests.clear(); // Unusual: clear every frame because this is rarely used.
if (table_temp_data.LastTimeActive >= 0.0f && table_temp_data.LastTimeActive < memory_compact_start_time)
TableGcCompactTransientBuffers(&table_temp_data);
}
if (g.GcCompactAll)
GcCompactTransientMiscBuffers();
g.GcCompactAll = false;

View File

@@ -2077,6 +2077,7 @@ enum ImGuiTableFlags_
ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0).
// Miscellaneous
ImGuiTableFlags_HighlightHoveredColumn = 1 << 28, // Highlight column headers when hovered (may evolve into a fuller highlight)
ImGuiTableFlags_TrackTopologyChanges = 1 << 29, // [BETA] Saved columns data keyed by their identifier. Allow data to persist after column addition/deletion/reordering. Requires valid identifier for all columns.
// [Internal] Combinations and masks
ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame,

View File

@@ -2893,7 +2893,7 @@ struct IMGUI_API ImGuiTabBar
#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color.
#define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx.
// [Internal] sizeof() ~ 112
// [Internal] sizeof() ~ 120
// We use the terminology "Enabled" to refer to a column that is not Hidden by user/api.
// We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping.
// This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped".
@@ -2935,6 +2935,8 @@ struct ImGuiTableColumn
bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not.
bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen).
bool IsPreserveWidthAuto;
bool IsJustCreated;
bool IsLoadedSettings;
ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte
ImU8 AutoFitQueue : 4; // Queue of 4 values for the next 4 frames to request auto-fit
ImU8 CannotSkipItemsQueue : 4; // Queue of 4 values for the next 4 frames to disable Clipped/SkipItem
@@ -2952,10 +2954,28 @@ struct ImGuiTableColumn
PrevEnabledColumn = NextEnabledColumn = -1;
SortOrder = -1;
SortDirection = ImGuiSortDirection_None;
IsJustCreated = true;
DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1;
}
};
// Passed to TableSetupColumn()
// sizeof() ~ 24+120 bytes
struct ImGuiTableReconcileColumnData
{
// Setup data
ImGuiID ID;
ImS16 NameOffset;
ImGuiTableColumnFlags Flags;
float InitWidthOrWeight;
ImGuiID UserData;
// Reconcile data
ImGuiTableColumnIdx ColumnNewIdx;
ImGuiTableColumnIdx ColumnOldIdx;
ImGuiTableColumn ColumnOldData; // Full backup of the column. Could be avoided by storing 1 of them and applying reconcile in the right order. Not worth bothering.
};
// Transient cell data stored per row.
// sizeof() ~ 6 bytes
struct ImGuiTableCellData
@@ -3117,7 +3137,7 @@ struct IMGUI_API ImGuiTable
// - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure.
// - We also leave out of this structure data that tend to be particularly useful for debugging/metrics.
// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs.
// sizeof() ~ 136 bytes.
// sizeof() ~ 176 bytes.
struct IMGUI_API ImGuiTableTempData
{
ImGuiID WindowID; // Shortcut to g.Tables[TableIndex]->OuterWindow->ID.
@@ -3126,6 +3146,11 @@ struct IMGUI_API ImGuiTableTempData
float AngledHeadersExtraWidth; // Used in EndTable()
ImVector<ImGuiTableHeaderData> AngledHeadersRequests; // Used in TableAngledHeadersRow() // FIXME: Single instance is enough?
// Topology change
ImVector<ImGuiTableReconcileColumnData> ReconcileColumnsRequests; // Used in TableSetupColumn(), TableUpdateLayout(). Cleared every frame.
void* OldColumnsRawData; // Used in BeginTable() -> TableUpdateLayout() when resizing.
ImSpan<ImGuiTableColumn> OldColumnsData;
ImVec2 UserOuterSize; // outer_size.x passed to BeginTable()
ImDrawListSplitter DrawSplitter;
@@ -3201,6 +3226,7 @@ namespace ImGui
IMGUI_API void TableBeginInitMemory(ImGuiTable* table, int columns_count);
IMGUI_API void TableApplyQueuedRequests(ImGuiTable* table);
IMGUI_API void TableSetupDrawChannels(ImGuiTable* table);
IMGUI_API void TableReconcileColumns(ImGuiTable* table);
IMGUI_API void TableUpdateLayout(ImGuiTable* table);
IMGUI_API void TableUpdateBorders(ImGuiTable* table);
IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table);
@@ -3214,6 +3240,7 @@ namespace ImGui
IMGUI_API void TableFixDisplayOrder(ImGuiTable* table);
IMGUI_API void TableSortSpecsSanitize(ImGuiTable* table);
IMGUI_API void TableSortSpecsBuild(ImGuiTable* table);
IMGUI_API void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask);
IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column);
IMGUI_API void TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column);
IMGUI_API float TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column);

View File

@@ -363,6 +363,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
g.TablesTempData.resize(g.TablesTempDataStacked, ImGuiTableTempData());
ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempData[g.TablesTempDataStacked - 1];
temp_data->TableIndex = table_idx;
temp_data->ReconcileColumnsRequests.resize(0); // FIXME: Use shrink(0) everywhere?
table->DrawSplitter = &table->TempData->DrawSplitter;
table->DrawSplitter->Clear();
@@ -563,15 +564,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->MemoryCompacted = false;
// Setup memory buffer (clear data if columns count changed)
ImGuiTableColumn* old_columns_to_preserve = NULL;
void* old_columns_raw_data = NULL;
const int old_columns_count = table->Columns.size();
if (old_columns_count != 0 && old_columns_count != columns_count)
{
// Attempt to preserve width and other settings on column count/specs change (#4046)
// Attempt to preserve width and other settings on column count/specs change (#4046, #9108)
IMGUI_DEBUG_LOG_TABLE("[table] Table 0x%08X column count %d -> %d, recreating storage.\n", table->ID, old_columns_count, columns_count);
old_columns_to_preserve = table->Columns.Data;
old_columns_raw_data = table->RawData; // Free at end of function
IM_ASSERT(temp_data->OldColumnsRawData == NULL);
temp_data->OldColumnsRawData = table->RawData; // Freed during layout
temp_data->OldColumnsData = table->Columns;
table->RawData = NULL;
}
if (table->RawData == NULL)
@@ -595,9 +595,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
for (int n = 0; n < columns_count; n++)
{
ImGuiTableColumn* column = &table->Columns[n];
if (old_columns_to_preserve && n < old_columns_count)
if (temp_data->OldColumnsData.Data && n < temp_data->OldColumnsData.size())
{
*column = old_columns_to_preserve[n];
*column = temp_data->OldColumnsData[n];
}
else
{
@@ -611,8 +611,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->DisplayOrderToIndex[n] = column->DisplayOrder;
}
}
if (old_columns_raw_data)
IM_FREE(old_columns_raw_data);
// Load settings
if (table->IsSettingsRequestLoad)
@@ -842,7 +840,41 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I
void ImGui::TableUpdateLayout(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
ImGuiTableTempData* temp_data = table->TempData;
IM_ASSERT(table->IsLayoutLocked == false);
const int columns_count = table->ColumnsCount;
// Reconcile moved columns
if (table->Flags & ImGuiTableFlags_TrackTopologyChanges)
{
IM_ASSERT_USER_ERROR(table->DeclColumnsCount == columns_count, "When using TrackTopologyChanges: must call TableSetupColumn() with a valid identifier for all columns!");
if (temp_data->ReconcileColumnsRequests.Size > 0)
TableReconcileColumns(table);
}
if (temp_data->OldColumnsRawData)
{
IM_FREE(temp_data->OldColumnsRawData);
temp_data->OldColumnsRawData = NULL;
temp_data->OldColumnsData.clear();
}
// Apply columns settings
if (table->IsSettingsRequestLoad)
TableLoadSettingsForColumns(table);
if (table->IsInitializing || table->IsSettingsRequestLoad)
{
for (ImGuiTableColumn& column : table->Columns)
{
ImGuiTableFlags init_flags;
if (table->IsSettingsRequestLoad)
init_flags = column.IsLoadedSettings ? ~table->SettingsLoadedFlags : ~0;
else
init_flags = column.IsJustCreated ? ~0 : 0;
TableInitColumnDefaults(table, &column, init_flags);
}
TableFixDisplayOrder(table); // Call even for non _Reorderable table as we loaded .ini data.
table->IsSettingsRequestLoad = false;
}
// Apply queued resizing/reordering/hiding requests
TableApplyQueuedRequests(table);
@@ -853,7 +885,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory.
// This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path.
const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ?
const int columns_count = table->ColumnsCount;
if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit)
{
const float scale_factor = new_ref_scale_unit / table->RefScale;
@@ -892,7 +923,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
{
TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None);
column->NameOffset = -1;
column->UserData = 0;
column->ID = column->UserData = 0;
column->InitStretchWeightOrWidth = -1.0f;
}
@@ -905,6 +936,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
table->IsSettingsDirty = true;
}
column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0;
column->IsJustCreated = false;
if (column->IsEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1))
table->IsDefaultVisibility = false;
@@ -1644,9 +1676,9 @@ void ImGui::EndTable()
NavUpdateCurrentWindowIsScrollPushableX();
}
// Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings.
// Called in TableUpdateLayout() when initializing/after loading settings.
// 'init_mask' specify which fields to initialize.
static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask)
void ImGui::TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask)
{
ImGuiTableColumnFlags flags = column->Flags;
if (init_mask & ImGuiTableFlags_Resizable)
@@ -1658,7 +1690,7 @@ static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column,
column->AutoFitQueue = 0x00;
}
if (init_mask & ImGuiTableFlags_Reorderable)
column->DisplayOrder = (ImGuiTableColumnIdx)table->Columns.index_from_ptr(column);
column->DisplayOrder = (ImGuiTableColumnIdx)((table->Flags & ImGuiTableFlags_Reorderable) ? -1 : table->Columns.index_from_ptr(column));
if (init_mask & ImGuiTableFlags_Hideable)
column->IsUserEnabled = column->IsUserEnabledNextFrame = (flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1;
if (init_mask & ImGuiTableFlags_Sortable)
@@ -1695,20 +1727,10 @@ static void TableSetupColumnApply(ImGuiTable* table, int idx, ImGuiID id, ImS16
column->ID = id;
column->UserData = user_data;
column->NameOffset = name_offset;
flags = column->Flags;
// Initialize defaults
column->InitStretchWeightOrWidth = init_width_or_weight;
if (table->IsInitializing)
{
ImGuiTableFlags init_flags = ~table->SettingsLoadedFlags;
if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f)
init_flags |= ImGuiTableFlags_Resizable;
TableInitColumnDefaults(table, column, init_flags);
}
}
void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data_for_sort_specs)
void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_data)
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
@@ -1725,10 +1747,63 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo
name_offset = (ImS16)table->ColumnsNames.size();
table->ColumnsNames.append(label, label + ImStrlen(label) + 1);
}
const ImGuiID column_id = (label != NULL && label[0] != 0) ? ImHashStr(label) : 0;
TableSetupColumnApply(table, table->DeclColumnsCount, column_id, name_offset, flags, init_width_or_weight, user_data_for_sort_specs);
table->DeclColumnsCount++;
// When ID changed or a column moved: defer the request until layout where we will process full reconcile.
const int column_idx = table->DeclColumnsCount++;
if (table->Flags & ImGuiTableFlags_TrackTopologyChanges)
{
// On column count change: search the previous columns:
// - the live Columns[] array is truncated on shrink, so a moved column may only exist in old data.
// - the live Columns[] array may already be partially reset by the TableInitColumnDefaults() call above.
// When count changed, even a never-updated slot (prev_column_id == 0 while initializing) may have old data to recover.
// Defer applying data, TableUpdateLayout() will call TableSetupColumnApply().
IM_ASSERT_USER_ERROR(column_id != 0, "When using TrackTopologyChanges: Must call TableSetupColumn() with a valid identifier for all columns!");
ImGuiTableColumn* column = &table->Columns[column_idx];
const bool may_have_old_data = (column->ID != 0 || !table->IsInitializing || !table->TempData->OldColumnsData.empty());
if (column->ID != column_id && column_id != 0 && may_have_old_data)
{
table->TempData->ReconcileColumnsRequests.push_back(ImGuiTableReconcileColumnData());
ImGuiTableReconcileColumnData& reconcile_data = table->TempData->ReconcileColumnsRequests.back();
reconcile_data.ID = column_id;
reconcile_data.NameOffset = name_offset;
reconcile_data.Flags = flags;
reconcile_data.InitWidthOrWeight = init_width_or_weight;
reconcile_data.UserData = user_data;
reconcile_data.ColumnNewIdx = (ImGuiTableColumnIdx)column_idx;
reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)-1;
ImSpan<ImGuiTableColumn>& search_columns = table->TempData->OldColumnsData.empty() ? table->Columns : table->TempData->OldColumnsData;
for (ImGuiTableColumn& old_column : search_columns)
if (old_column.ID == column_id)
{
reconcile_data.ColumnOldIdx = (ImGuiTableColumnIdx)search_columns.index_from_ptr(&old_column);
reconcile_data.ColumnOldData = old_column;
break;
}
column->NameOffset = name_offset; // Allow TableGetColumnName() to work before layout.
return;
}
}
TableSetupColumnApply(table, column_idx, column_id, name_offset, flags, init_width_or_weight, user_data);
}
void ImGui::TableReconcileColumns(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
ImGuiTableTempData* temp_data = table->TempData;
IM_UNUSED(g);
IMGUI_DEBUG_LOG_TABLE("[table] Reconcile moved columns for table 0x%08X\n", table->ID);
for (ImGuiTableReconcileColumnData& reconcile_data : temp_data->ReconcileColumnsRequests)
{
ImGuiTableColumn* column = &table->Columns[reconcile_data.ColumnNewIdx];
*column = reconcile_data.ColumnOldData; // When old column was not found clear anyway with default-constructed data.
TableSetupColumnApply(table, reconcile_data.ColumnNewIdx, reconcile_data.ID, reconcile_data.NameOffset, reconcile_data.Flags, reconcile_data.InitWidthOrWeight, reconcile_data.UserData);
IMGUI_DEBUG_LOG_TABLE("[table] - old %d -> new %d \"%s\"\n", reconcile_data.ColumnOldIdx, reconcile_data.ColumnNewIdx, TableGetColumnName(table, reconcile_data.ColumnNewIdx)); // Log at the end so NameOffset was copied.
}
TableFixDisplayOrder(table);
table->IsSettingsDirty = true; // FIXME-RECONCILE: Necessary?
temp_data->ReconcileColumnsRequests.resize(0); // GC-ed once in NewFrame()
}
// [Public]
@@ -3778,9 +3853,7 @@ ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table)
ImGuiContext& g = *GImGui;
ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset);
IM_ASSERT(settings->ID == table->ID);
if (settings->ColumnsCountMax >= table->ColumnsCount)
return settings; // OK
settings->ID = 0; // Invalidate storage, we won't fit because of a count change
return settings;
}
return NULL;
}
@@ -3803,6 +3876,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table)
// Bind or create settings data
ImGuiContext& g = *GImGui;
ImGuiTableSettings* settings = TableGetBoundSettings(table);
if (settings != NULL && table->ColumnsCount > settings->ColumnsCountMax)
{
settings->ID = 0; // Invalidate storage, we won't fit because of a count change
settings = NULL;
}
if (settings == NULL)
{
settings = TableSettingsCreate(table->ID, table->ColumnsCount);
@@ -3818,6 +3896,8 @@ void ImGui::TableSaveSettings(ImGuiTable* table)
bool save_ref_scale = false;
settings->SaveFlags = ImGuiTableFlags_None;
if (table->Flags & ImGuiTableFlags_TrackTopologyChanges)
settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable;
for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++)
{
const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest;
@@ -3853,9 +3933,11 @@ void ImGui::TableSaveSettings(ImGuiTable* table)
void ImGui::TableLoadSettings(ImGuiTable* table)
{
ImGuiContext& g = *GImGui;
table->IsSettingsRequestLoad = false;
if (table->Flags & ImGuiTableFlags_NoSavedSettings)
{
table->IsSettingsRequestLoad = false; // Done
return;
}
// Bind settings
ImGuiTableSettings* settings;
@@ -3875,30 +3957,50 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
table->SettingsLoadedFlags = settings->SaveFlags;
table->RefScale = settings->RefScale;
// (TableUpdateLayout() will further read from settings data)
}
// Initialize default columns settings
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
{
ImGuiTableColumn* column = &table->Columns[column_n];
TableInitColumnDefaults(table, column, ~0);
column->AutoFitQueue = 0x00;
}
void ImGui::TableLoadSettingsForColumns(ImGuiTable* table)
{
for (ImGuiTableColumn& column : table->Columns)
column.IsLoadedSettings = false;
ImGuiTableSettings* settings = TableGetBoundSettings(table);
if (settings == NULL)
return;
// When using TrackTopologyChanges mode, columns ID are either all sets, either all cleared (initially).
//const bool has_columns_id = table->Columns[0].ID != 0;
// Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn
ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();
for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++)
{
int column_n = column_settings->Index;
if ((table->Flags & ImGuiTableFlags_TrackTopologyChanges) && (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges))// && has_columns_id)
if (column_n >= table->ColumnsCount || table->Columns[column_n].ID != column_settings->ID)
{
// - Case 1: done during initial table creation: we don't have ID yet. Write back loaded IDs, will reconcile in TableUpdateLayout().
// - Case 2 (this code): we have columns identifiers already.
column_n = -1;
for (int other_n = 0; other_n < table->ColumnsCount && column_n == -1; other_n++)
if (table->Columns[other_n].ID == column_settings->ID)
column_n = other_n;
}
if (column_n < 0 || column_n >= table->ColumnsCount)
continue;
ImGuiTableColumn* column = &table->Columns[column_n];
column->IsLoadedSettings = true;
if (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges)
column->ID = column_settings->ID;
if (settings->SaveFlags & ImGuiTableFlags_Resizable)
{
if (column_settings->IsStretch)
column->StretchWeight = column_settings->WidthOrWeight;
else
column->WidthRequest = column_settings->WidthOrWeight;
column->AutoFitQueue = 0x00;
}
if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
@@ -3907,12 +4009,6 @@ void ImGui::TableLoadSettings(ImGuiTable* table)
column->SortOrder = column_settings->SortOrder;
column->SortDirection = column_settings->SortDirection;
}
// Fix display order and build index
if (settings->SaveFlags & ImGuiTableFlags_Reorderable)
TableFixDisplayOrder(table);
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;
}
struct ImGuiTableFixDisplayOrderColumnData
@@ -3927,7 +4023,8 @@ static int IMGUI_CDECL TableFixDisplayOrderComparer(const void* lhs, const void*
const ImGuiTable* table = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Table;
const ImGuiTableColumnIdx lhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)lhs)->Idx;
const ImGuiTableColumnIdx rhs_idx = ((const ImGuiTableFixDisplayOrderColumnData*)rhs)->Idx;
const int order_delta = (table->Columns[lhs_idx].DisplayOrder - table->Columns[rhs_idx].DisplayOrder);
// Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after.
const int order_delta = ((unsigned short)table->Columns[lhs_idx].DisplayOrder - (unsigned short)table->Columns[rhs_idx].DisplayOrder);
return (order_delta > 0) ? +1 : (order_delta < 0) ? -1 : (lhs_idx > rhs_idx) ? +1 : -1;
}
@@ -3945,6 +4042,8 @@ void ImGui::TableFixDisplayOrder(ImGuiTable* table)
ImQsort(fdo_columns, (size_t)table->ColumnsCount, sizeof(ImGuiTableFixDisplayOrderColumnData), TableFixDisplayOrderComparer);
for (int n = 0; n < table->ColumnsCount; n++)
table->Columns[fdo_columns[n].Idx].DisplayOrder = (ImGuiTableColumnIdx)n;
for (int n = 0; n < table->ColumnsCount; n++)
table->DisplayOrderToIndex[table->Columns[n].DisplayOrder] = (ImGuiTableColumnIdx)n;
}
static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
@@ -4010,7 +4109,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*,
if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; }
if (sscanf(line, "Order=%d%n", &n, &r) == 1) { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; }
if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; }
if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; }
if (sscanf(line, "ID=0x%08X%n", (ImU32*)&n, &r) == 1) { line = ImStrSkipBlank(line + r); column->ID = (ImGuiID)n; settings->SaveFlags |= ImGuiTableFlags_TrackTopologyChanges; }
}
}
@@ -4028,6 +4127,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle
const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0;
const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0;
const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0;
const bool save_id = (settings->SaveFlags & ImGuiTableFlags_TrackTopologyChanges) != 0;
// We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings".
buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve
@@ -4047,7 +4147,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle
if (save_visible) { buf->appendf(" Visible=%d", column->IsEnabled); }
if (save_order) { buf->appendf(" Order=%d", column->DisplayOrder); }
if (save_sort && column->SortOrder != -1) { buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); }
if (column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); }
if (save_id && column->ID != 0) { buf->appendf(" ID=0x%08X", column->ID); }
buf->append("\n");
}
buf->append("\n");