From 561a92d557742deaf0405318600e91bbe65ab81b Mon Sep 17 00:00:00 2001
From: Patrick Wuttke
Date: Tue, 28 Oct 2025 12:05:17 +0100
Subject: [PATCH] ImRaid: added TextUnformatted() for string_views; added
hovering/clicking and some more minor improvements to data tables; fixed
signature of c_MessageBox and added c_MessageBoxText.
---
public/raid/imraid.hpp | 158 ++++++++++++++++++++++++++++++++---------
1 file changed, 124 insertions(+), 34 deletions(-)
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)