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.
This commit is contained in:
@@ -5,11 +5,14 @@
|
||||
#define RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED 1
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -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<std::size_t> 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<typename TObject>
|
||||
struct DataTableOptions
|
||||
{
|
||||
using filter_t = std::function<bool(const TObject&)>;
|
||||
using getid_t = std::function<const char*(const TObject&)>;
|
||||
using click_handler_t = std::function<void(const TObject&)>;
|
||||
using is_selected_t = std::function<bool(const TObject&)>;
|
||||
using select_handler_t = std::function<void(const TObject&, SelectAction)>;
|
||||
|
||||
std::span<const DataTableColumn<TObject>> 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<typename TObject,typename TData>
|
||||
inline void DataTable(const char* strId, const DataTableOptions<TObject>& options, const TData& data, DataTableState& state, ImVec2 outerSize = {})
|
||||
template<typename TObject, typename TData>
|
||||
inline void DataTable(const char* strId, const DataTableOptions<TObject>& 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<TObject>& option
|
||||
|
||||
for (const DataTableColumn<TObject>& 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<TObject>& option
|
||||
}
|
||||
}
|
||||
|
||||
if (state.sortedIndices.size() != data.size())
|
||||
{
|
||||
state.dirty = true;
|
||||
state.sortedIndices.resize(data.size());
|
||||
}
|
||||
static constexpr bool kSortable = std::ranges::random_access_range<TData>;
|
||||
|
||||
if (state.dirty)
|
||||
typename DataTableOptions<TObject>::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<std::uintptr_t>(&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<TObject>& 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<TData>(data)[dataIdx]);
|
||||
}
|
||||
}
|
||||
else // constexpr kSortable
|
||||
{
|
||||
for (const TObject& object : std::forward<TData>(data)) {
|
||||
renderRow(object);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
@@ -249,7 +332,7 @@ mijin::Task<> c_MessageBox(const char* titleId, TFunc&& renderFunc)
|
||||
}
|
||||
|
||||
template<typename... TFormatArgs>
|
||||
mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char*> buttons, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char* const> buttons, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
const std::string message = std::format(format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
int buttonIdx = -1;
|
||||
@@ -307,7 +390,7 @@ struct YesNo
|
||||
template<YesNo::Value DefaultResult = YesNo::NO, typename... TFormatArgs>
|
||||
mijin::Task<YesNo> c_MessageBoxYesNo(const char* titleId, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
static std::array<const char*, 2> BUTTONS = {"Yes", "No"};
|
||||
static const std::array<const char*, 2> BUTTONS = {"Yes", "No"};
|
||||
const int idx = co_await c_MessageBox(titleId, BUTTONS, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
switch (idx)
|
||||
{
|
||||
@@ -319,6 +402,13 @@ mijin::Task<YesNo> c_MessageBoxYesNo(const char* titleId, std::format_string<TFo
|
||||
co_return DefaultResult;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... TFormatArgs>
|
||||
mijin::Task<> c_MessageBoxText(const char* titleId, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
static const char* const BUTTON_TEXT = "OK";
|
||||
co_await c_MessageBox(titleId, {&BUTTON_TEXT, 1}, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
}
|
||||
} // namespace ImRaid
|
||||
|
||||
#endif // !defined(RAID_PUBLIC_RAID_IMRAID_HPP_INCLUDED)
|
||||
|
||||
Reference in New Issue
Block a user