diff --git a/public/raid/imraid.hpp b/public/raid/imraid.hpp index 2082517..e7fc049 100644 --- a/public/raid/imraid.hpp +++ b/public/raid/imraid.hpp @@ -5,11 +5,14 @@ #define RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED 1 #include +#include #include #include #include +#include #include #include +#include #include #include @@ -73,6 +76,11 @@ inline bool BeginPopupButton(const char* label) return ImGui::BeginPopup(popupId, ImGuiWindowFlags_NoNav); } +inline void TextUnformatted(std::string_view stringView) +{ + ImGui::TextUnformatted(stringView.data(), stringView.data() + stringView.size()); +} + struct DataTableState { std::vector sortedIndices; @@ -99,18 +107,40 @@ struct DataTableColumn const char* header; renderer_t renderer; comparator_t comparator; + ImGuiTableColumnFlags flags = ImGuiTableColumnFlags_None; + float initialWidthOrWeight = 0.f; +}; + +enum class SelectAction +{ + ADD, + REMOVE, + SET }; template struct DataTableOptions { + using filter_t = std::function; + using getid_t = std::function; + using click_handler_t = std::function; + using is_selected_t = std::function; + using select_handler_t = std::function; + std::span> columns; ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SortMulti; + bool hoverable = false; + + filter_t filter = {}; + getid_t getId = {}; + click_handler_t rightClickHandler = {}; + is_selected_t isSelected = {}; + select_handler_t selectHandler = {}; }; -template -inline void DataTable(const char* strId, const DataTableOptions& options, const TData& data, DataTableState& state, ImVec2 outerSize = {}) +template +inline void DataTable(const char* strId, const DataTableOptions& options, TData&& data, DataTableState& state, ImVec2 outerSize = {}) { if (outerSize.y <= 0.f) { outerSize.y = ImGui::GetContentRegionAvail().y; @@ -122,12 +152,12 @@ inline void DataTable(const char* strId, const DataTableOptions& option for (const DataTableColumn& column : options.columns) { - ImGuiTableColumnFlags flags = 0; + ImGuiTableColumnFlags flags = column.flags; MIJIN_ASSERT(column.renderer, "Missing column renderer."); if (!column.comparator) { flags |= ImGuiTableColumnFlags_NoSort; } - ImGui::TableSetupColumn(column.header, flags); + ImGui::TableSetupColumn(column.header, flags, column.initialWidthOrWeight); } ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); @@ -156,49 +186,102 @@ inline void DataTable(const char* strId, const DataTableOptions& option } } - if (state.sortedIndices.size() != data.size()) - { - state.dirty = true; - state.sortedIndices.resize(data.size()); - } + static constexpr bool kSortable = std::ranges::random_access_range; - if (state.dirty) + typename DataTableOptions::getid_t getId = options.getId; + + static constexpr std::size_t kGetIdBufferSize = 19; // 16 digits + ## and \0 + char getIdBuffer[kGetIdBufferSize] = {0}; + if (!getId) { - 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) + getId = [&](const TObject& object) { - for (const DataTableState::SortColumn& column : state.sortColumns) - { - const bool less = options.columns[column.index].comparator(data[leftIdx], data[rightIdx]); - if (less) - { // left < right - return !column.sortDescending; - } - if (options.columns[column.index].comparator(data[rightIdx], data[leftIdx])) - { // left > right - return column.sortDescending; - } - } - // left == right - return false; - }); - state.dirty = false; + std::snprintf(getIdBuffer, kGetIdBufferSize, "##%016" PRIXPTR, reinterpret_cast(&object)); + return getIdBuffer; + }; } + + std::size_t rowIdx = 0; - for (const std::size_t dataIdx : state.sortedIndices) + auto renderRow = [&](const TObject& object) { - const TObject& object = data[dataIdx]; + if (options.filter && !options.filter(object)) { + return; + } + ImGui::TableNextRow(); + bool hoverNext = options.hoverable; + const bool selected = options.isSelected ? options.isSelected(object) : false; for (const DataTableColumn& column : options.columns) { ImGui::TableNextColumn(); + if (hoverNext) + { + hoverNext = false; + if (ImGui::Selectable(getId(object), selected, ImGuiSelectableFlags_SpanAllColumns) && options.selectHandler) + { + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) { + options.selectHandler(object, selected ? SelectAction::REMOVE : SelectAction::ADD); + } + else if (!selected) { + options.selectHandler(object, SelectAction::SET); + } + } + if (options.rightClickHandler && ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { + options.rightClickHandler(object); + } + ImGui::SameLine(); + } column.renderer({ .object = object }); } + + ++rowIdx; + }; + if constexpr (kSortable) + { + 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) + { + for (const DataTableState::SortColumn& column : state.sortColumns) + { + const bool less = options.columns[column.index].comparator(data[leftIdx], data[rightIdx]); + if (less) + { // left < right + return !column.sortDescending; + } + if (options.columns[column.index].comparator(data[rightIdx], data[leftIdx])) + { // left > right + return column.sortDescending; + } + } + // left == right + return false; + }); + state.dirty = false; + } + + for (const std::size_t dataIdx : state.sortedIndices) { + renderRow(std::forward(data)[dataIdx]); + } + } + else // constexpr kSortable + { + for (const TObject& object : std::forward(data)) { + renderRow(object); + } } ImGui::EndTable(); @@ -249,7 +332,7 @@ mijin::Task<> c_MessageBox(const char* titleId, TFunc&& renderFunc) } template -mijin::Task c_MessageBox(const char* titleId, std::span buttons, std::format_string format, TFormatArgs&&... formatArgs) +mijin::Task c_MessageBox(const char* titleId, std::span buttons, std::format_string format, TFormatArgs&&... formatArgs) { const std::string message = std::format(format, std::forward(formatArgs)...); int buttonIdx = -1; @@ -307,7 +390,7 @@ struct YesNo template mijin::Task c_MessageBoxYesNo(const char* titleId, std::format_string format, TFormatArgs&&... formatArgs) { - static std::array BUTTONS = {"Yes", "No"}; + static const std::array BUTTONS = {"Yes", "No"}; const int idx = co_await c_MessageBox(titleId, BUTTONS, format, std::forward(formatArgs)...); switch (idx) { @@ -319,6 +402,13 @@ mijin::Task c_MessageBoxYesNo(const char* titleId, std::format_string +mijin::Task<> c_MessageBoxText(const char* titleId, std::format_string format, TFormatArgs&&... formatArgs) +{ + static const char* const BUTTON_TEXT = "OK"; + co_await c_MessageBox(titleId, {&BUTTON_TEXT, 1}, format, std::forward(formatArgs)...); +} } // namespace ImRaid #endif // !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)