diff --git a/public/raid/imraid.hpp b/public/raid/imraid.hpp index 179e858..a59edb5 100644 --- a/public/raid/imraid.hpp +++ b/public/raid/imraid.hpp @@ -4,7 +4,14 @@ #if !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED) #define RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED 1 +#include +#include +#include +#include +#include + #include +#include namespace ImRaid { @@ -47,6 +54,106 @@ inline bool ToggleImageButton(const char* strId, ImTextureID textureId, const Im } return clicked; } + +struct DataTableState +{ + std::vector sortedIndices; + std::size_t sortColumn = 0; + bool sortDescending = false; + bool dirty = true; +}; + +template +struct DataTableColumn +{ + struct CellRendererArgs + { + const TObject& object; + }; + using renderer_t = std::function; + using comparator_t = std::function; + + std::string header; + renderer_t renderer; + comparator_t comparator; +}; + +template +struct DataTableOptions +{ + std::span> columns; + ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable | ImGuiTableFlags_ScrollY; +}; + +template +inline void DataTable(const char* strId, const DataTableOptions& options, const TData& data, DataTableState& state, ImVec2 outerSize = {}) +{ + if (outerSize.y <= 0.f) { + outerSize.y = ImGui::GetContentRegionAvail().y; + } + + if (!ImGui::BeginTable(strId, static_cast(options.columns.size()), options.tableFlags, outerSize)) { + return; + } + + for (const DataTableColumn& column : options.columns) + { + ImGuiTableColumnFlags flags = 0; + MIJIN_ASSERT(column.renderer, "Missing column renderer."); + if (!column.comparator) { + flags |= ImGuiTableColumnFlags_NoSort; + } + ImGui::TableSetupColumn(column.header.c_str(), flags); + } + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs(); + if (sortSpecs != nullptr && sortSpecs->SpecsDirty) + { + sortSpecs->SpecsDirty = false; + + const ImGuiTableColumnSortSpecs& specs = *sortSpecs->Specs; + state.dirty |= (specs.ColumnIndex != state.sortColumn); + state.sortColumn = specs.ColumnIndex; + state.sortDescending = specs.SortDirection == ImGuiSortDirection_Descending; + } + + if (state.sortedIndices.size() != data.size()) + { + state.dirty = true; + state.sortedIndices.resize(data.size()); + } + + if (state.dirty) + { + for (std::size_t idx = 0; idx < data.size(); ++idx) { + state.sortedIndices[idx] = idx; + } + std::ranges::sort(state.sortedIndices, [&](std::size_t leftIdx, std::size_t rightIdx) { + return options.columns[state.sortColumn].comparator(data[leftIdx], data[rightIdx]); + }); + state.dirty = false; + } + + for (std::size_t indexIdx = 0; indexIdx < state.sortedIndices.size(); ++indexIdx) + { + const std::size_t dataIdx = state.sortDescending ? state.sortedIndices[state.sortedIndices.size() - indexIdx - 1] + : state.sortedIndices[indexIdx]; + const TObject& object = data[dataIdx]; + ImGui::TableNextRow(); + + for (const DataTableColumn& column : options.columns) + { + ImGui::TableNextColumn(); + column.renderer({ + .object = object + }); + } + } + + ImGui::EndTable(); +} } // namespace ImRaid #endif // !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)