From 2f94e7dc13046715abb570891f54e2c3198e39fc Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 8 Jun 2026 17:40:05 +0200 Subject: [PATCH] Tables: add support for ImGuiTableFlags_TrackTopologyChanges (#9108, #4046) --- imgui.cpp | 3 + imgui.h | 1 + imgui_internal.h | 31 +++++++- imgui_tables.cpp | 196 +++++++++++++++++++++++++++++++++++------------ 4 files changed, 181 insertions(+), 50 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 56860dbd5..c45606863 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -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; diff --git a/imgui.h b/imgui.h index 720e3c8cd..e4a8d0e07 100644 --- a/imgui.h +++ b/imgui.h @@ -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, diff --git a/imgui_internal.h b/imgui_internal.h index ffd9d7658..ff66e2989 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -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 AngledHeadersRequests; // Used in TableAngledHeadersRow() // FIXME: Single instance is enough? + // Topology change + ImVector ReconcileColumnsRequests; // Used in TableSetupColumn(), TableUpdateLayout(). Cleared every frame. + void* OldColumnsRawData; // Used in BeginTable() -> TableUpdateLayout() when resizing. + ImSpan 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); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a028e3b33..01a03e4d3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -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& 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");