overlay: add sorting to patches table (#761)

This commit is contained in:
bicarus
2026-06-15 14:47:15 -07:00
committed by GitHub
parent bf05a3ef30
commit 29d3d9bb52
2 changed files with 86 additions and 4 deletions
+77 -4
View File
@@ -46,6 +46,11 @@ using namespace rapidjson;
namespace overlay::windows { namespace overlay::windows {
// user-assigned IDs for the patches table columns, used by the sort logic
enum PatchColumnId {
PATCH_COLUMN_NAME = 0,
PATCH_COLUMN_STATUS = 1,
};
robin_hood::unordered_map<std::string, std::unique_ptr<std::vector<uint8_t>>> DLL_MAP; robin_hood::unordered_map<std::string, std::unique_ptr<std::vector<uint8_t>>> DLL_MAP;
robin_hood::unordered_map<std::string, std::unique_ptr<std::vector<uint8_t>>> DLL_MAP_ORG; robin_hood::unordered_map<std::string, std::unique_ptr<std::vector<uint8_t>>> DLL_MAP_ORG;
@@ -218,6 +223,7 @@ namespace overlay::windows {
// patches // patches
std::vector<PatchData> PatchManager::patches; std::vector<PatchData> PatchManager::patches;
bool PatchManager::local_patches_initialized = false; bool PatchManager::local_patches_initialized = false;
std::vector<size_t> PatchManager::patches_sorted;
// loader notifications // loader notifications
bool ldr_registered = false; bool ldr_registered = false;
@@ -649,13 +655,27 @@ namespace overlay::windows {
// draw patches // draw patches
ImGui::PushStyleVarY(ImGuiStyleVar_CellPadding, ImGui::PushStyleVarY(ImGuiStyleVar_CellPadding,
ImGui::GetStyle().CellPadding.y + overlay::apply_scaling(2)); ImGui::GetStyle().CellPadding.y + overlay::apply_scaling(2));
if (ImGui::BeginTable("PatchesTable", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) { if (ImGui::BeginTable("PatchesTable", 2,
ImGui::TableSetupColumn("##NameColumn", ImGuiTableColumnFlags_WidthStretch); ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg
ImGui::TableSetupColumn("##OptionsColumn", ImGuiTableColumnFlags_WidthFixed, 240); | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortTristate)) {
ImGui::TableSetupColumn(
"Patch",
ImGuiTableColumnFlags_WidthStretch,
0.f, PATCH_COLUMN_NAME);
ImGui::TableSetupColumn(
"Status",
ImGuiTableColumnFlags_WidthFixed,
240, PATCH_COLUMN_STATUS);
ImGui::TableHeadersRow();
// maintain a sorted view of patches so the underlying vector
// order (used by config save and hashing) is left untouched
update_sorted_patches();
const auto search_str_in_lower = strtolower(patch_name_filter); const auto search_str_in_lower = strtolower(patch_name_filter);
size_t patches_shown = 0; size_t patches_shown = 0;
for (auto &patch : patches) { for (auto patch_index : patches_sorted) {
auto &patch = patches[patch_index];
// get patch status // get patch status
PatchStatus patch_status = is_patch_active(patch); PatchStatus patch_status = is_patch_active(patch);
@@ -881,6 +901,55 @@ namespace overlay::windows {
} }
} }
void PatchManager::update_sorted_patches() {
// Rebuild/re-sort only when the sort order changes (SpecsDirty) or when
// the patch list count changed - not every frame. reload_local_patches()
// clears the cache to avoid dangling pointers when `patches` is rebuilt.
auto *sort_specs = ImGui::TableGetSortSpecs();
const bool patches_changed = patches_sorted.size() != patches.size();
if (!patches_changed && !(sort_specs && sort_specs->SpecsDirty)) {
return;
}
// rebuild the view in the underlying vector order (this is also the
// default order shown when the sort is cleared / tristate "unsorted")
patches_sorted.clear();
patches_sorted.reserve(patches.size());
for (size_t i = 0; i < patches.size(); i++) {
patches_sorted.push_back(i);
}
// SpecsCount == 0 means no active sort: keep the default order
if (sort_specs && sort_specs->SpecsCount > 0) {
const auto &spec = sort_specs->Specs[0];
const bool ascending = spec.SortDirection != ImGuiSortDirection_Descending;
std::stable_sort(patches_sorted.begin(), patches_sorted.end(),
[&](size_t ia, size_t ib) {
const PatchData *a = &patches[ia];
const PatchData *b = &patches[ib];
int cmp;
if (spec.ColumnUserID == PATCH_COLUMN_STATUS) {
// sort by displayed status (matches the checkbox), then
// by name as a tiebreaker
const bool a_on = a->last_status == PatchStatus::Enabled;
const bool b_on = b->last_status == PatchStatus::Enabled;
if (a_on != b_on) {
cmp = a_on ? -1 : 1;
} else {
cmp = a->name_in_lower_case.compare(b->name_in_lower_case);
}
} else {
cmp = a->name_in_lower_case.compare(b->name_in_lower_case);
}
return ascending ? (cmp < 0) : (cmp > 0);
});
}
if (sort_specs) {
sort_specs->SpecsDirty = false;
}
}
void PatchManager::show_patch_tooltip(const PatchData& patch) { void PatchManager::show_patch_tooltip(const PatchData& patch) {
if (!patch.caution.empty()) { if (!patch.caution.empty()) {
ImGui::WarnTooltip(patch.description.c_str(), patch.caution.c_str()); ImGui::WarnTooltip(patch.description.c_str(), patch.caution.c_str());
@@ -1753,6 +1822,9 @@ namespace overlay::windows {
// clear old patches // clear old patches
patches.clear(); patches.clear();
// drop the cached sorted view so the table rebuilds it (in default order)
// on the next frame
patches_sorted.clear();
if (cfg::CONFIGURATOR_STANDALONE) { if (cfg::CONFIGURATOR_STANDALONE) {
DLL_MAP.clear(); DLL_MAP.clear();
DLL_MAP_ORG.clear(); DLL_MAP_ORG.clear();
@@ -1807,6 +1879,7 @@ namespace overlay::windows {
bool imported = false; bool imported = false;
// clear old patches // clear old patches
patches.clear(); patches.clear();
patches_sorted.clear();
url_fetch_errors.clear(); url_fetch_errors.clear();
// load patches for main dll // load patches for main dll
@@ -133,9 +133,18 @@ namespace overlay::windows {
static std::vector<PatchData> patches; static std::vector<PatchData> patches;
static bool local_patches_initialized; static bool local_patches_initialized;
// cached sorted view of `patches` for the table, stored as indices so a
// stale entry can never dangle if `patches` is rebuilt; rebuilt only when
// the sort order changes or the patch list is reloaded (not every frame)
static std::vector<size_t> patches_sorted;
void config_load(); void config_load();
void config_save(); void config_save();
// rebuild the cached sorted view of `patches` from the active table's
// sort specs; no-op unless the sort changed or the patch list changed
void update_sorted_patches();
void append_patches( void append_patches(
std::string &patches_json, std::string &patches_json,
bool apply_patches = false, bool apply_patches = false,