diff --git a/private/raid/SModule b/private/raid/SModule index 6874b80..7f62d86 100644 --- a/private/raid/SModule +++ b/private/raid/SModule @@ -9,6 +9,7 @@ if not hasattr(env, 'Jinja'): src_files = Split(""" application.cpp config.cpp + imutil.cpp fonts.gen.cpp stb_image.cpp """) diff --git a/private/raid/imutil.cpp b/private/raid/imutil.cpp new file mode 100644 index 0000000..c5e6819 --- /dev/null +++ b/private/raid/imutil.cpp @@ -0,0 +1,7 @@ + +#include "raid/imutil.hpp" + +namespace raid::impl +{ +// std::string gFormatBuffer; +} diff --git a/public/raid/imraid.hpp b/public/raid/imraid.hpp index 2bc41b2..6df05a9 100644 --- a/public/raid/imraid.hpp +++ b/public/raid/imraid.hpp @@ -16,8 +16,13 @@ #include #include +#include #include #include +#include +#include + +#include "./imutil.hpp" namespace ImRaid { @@ -61,7 +66,7 @@ inline bool ToggleImageButton(const char* strId, ImTextureID textureId, const Im return clicked; } -inline bool BeginPopupButton(const char* label) +inline bool BeginPopupButton(const char* label, const ImVec2& size = {} , ImGuiWindowFlags flags = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings) { char popupId[128] = {"popup##"}; std::strcat(popupId, label); @@ -73,7 +78,8 @@ inline bool BeginPopupButton(const char* label) const float popupY = ImGui::GetCursorScreenPos().y; ImGui::SetNextWindowPos({popupX, popupY}); - return ImGui::BeginPopup(popupId, ImGuiWindowFlags_NoNav); + ImGui::SetNextWindowSize(size); + return ImGui::BeginPopup(popupId, flags); } inline void TextUnformatted(std::string_view stringView) @@ -81,6 +87,11 @@ inline void TextUnformatted(std::string_view stringView) ImGui::TextUnformatted(stringView.data(), stringView.data() + stringView.size()); } +inline float CalcButtonWidth(const char* text) +{ + return ImGui::CalcTextSize(text).x + (2.f * ImGui::GetStyle().FramePadding.x); +} + class IDScope { public: @@ -138,6 +149,21 @@ struct DataTableColumn float initialWidthOrWeight = 0.f; }; +struct DataTableItemState +{ + bool selected = false; + bool hovered = false; +}; + +template +struct DataTableRenderState +{ + const TObject* currentObject = nullptr; + std::size_t rowIdx = 0; + unsigned currentTreeDepth = 0; + DataTableItemState item = {}; +}; + enum class SelectAction { ADD, @@ -145,29 +171,40 @@ enum class SelectAction SET }; +struct DataTableTreeItem +{ + unsigned depth = 0; + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_SpanAllColumns; +}; + 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 getstate_t = std::function; using select_handler_t = std::function; + using gettreeitem_t = std::function; std::span> columns; ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SortMulti; bool hoverable = false; + bool tree = false; + bool noHeaders = false; filter_t filter = {}; getid_t getId = {}; click_handler_t rightClickHandler = {}; - is_selected_t isSelected = {}; + getstate_t getState = {}; select_handler_t selectHandler = {}; + gettreeitem_t getTreeItem = {}; }; template -inline void DataTable(const char* strId, const DataTableOptions& options, TData&& data, DataTableState& state, ImVec2 outerSize = {}) +inline void DataTable(const char* strId, const DataTableOptions& options, TData&& data, DataTableState& state, DataTableRenderState& renderState, ImVec2 outerSize = {}) { if (outerSize.y <= 0.f) { outerSize.y = ImGui::GetContentRegionAvail().y; @@ -186,8 +223,12 @@ inline void DataTable(const char* strId, const DataTableOptions& option } ImGui::TableSetupColumn(column.header, flags, column.initialWidthOrWeight); } - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableHeadersRow(); + + if (!options.noHeaders) + { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + } ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs(); if (sortSpecs != nullptr && sortSpecs->SpecsDirty) @@ -227,35 +268,74 @@ inline void DataTable(const char* strId, const DataTableOptions& option return getIdBuffer; }; } - - std::size_t rowIdx = 0; + typename DataTableOptions::gettreeitem_t getTreeItem = options.getTreeItem; + if (!getTreeItem) + { + getTreeItem = [](const TObject&) -> DataTableTreeItem { + return {}; + }; + } + + renderState.rowIdx = 0; + renderState.currentTreeDepth = 0; + auto renderRow = [&](const TObject& object) { + if (options.tree && getTreeItem(object).depth > renderState.currentTreeDepth) { + return; + } + if (options.filter && !options.filter(object)) { return; } ImGui::TableNextRow(); + renderState.currentObject = &object; - bool hoverNext = options.hoverable; - const bool selected = options.isSelected ? options.isSelected(object) : false; + bool hoverNext = options.hoverable || options.tree; + renderState.item = options.getState ? options.getState(object) : DataTableItemState{}; for (const DataTableColumn& column : options.columns) { ImGui::TableNextColumn(); if (hoverNext) { hoverNext = false; - if (ImGui::Selectable(getId(object), selected, ImGuiSelectableFlags_SpanAllColumns) && options.selectHandler) + + bool clicked = false; + if (options.tree) + { + const DataTableTreeItem treeItem = getTreeItem(object); + MIJIN_ASSERT(treeItem.depth <= renderState.currentTreeDepth + 1, "Tree depth cannot increase by more than 1 per item."); + + // close up to new depth + for (unsigned cnt = treeItem.depth; cnt < renderState.currentTreeDepth; ++cnt) { + ImGui::TreePop(); + } + + const ImGuiTreeNodeFlags flags = treeItem.flags + | (renderState.item.selected ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None); + const bool open = ImGui::TreeNodeEx(getId(object), flags, ""); + renderState.currentTreeDepth = treeItem.depth + (open ? 1 : 0); + clicked = ImGui::IsItemClicked(); + } + else + { + const ImGuiSelectableFlags flags = ImGuiSelectableFlags_SpanAllColumns + | (renderState.item.hovered ? ImGuiSelectableFlags_Highlight : ImGuiSelectableFlags_None); + clicked = ImGui::Selectable(getId(object), renderState.item.selected, flags); + } + + if (clicked && options.selectHandler) { if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) { - options.selectHandler(object, selected ? SelectAction::REMOVE : SelectAction::ADD); + options.selectHandler(object, renderState.item.selected ? SelectAction::REMOVE : SelectAction::ADD); } - else if (!selected) { + else if (!renderState.item.selected) { options.selectHandler(object, SelectAction::SET); } } - if (options.rightClickHandler && ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered()) { + if (options.rightClickHandler && ImGui::IsItemClicked(ImGuiMouseButton_Right)) { options.rightClickHandler(object); } ImGui::SameLine(); @@ -265,8 +345,9 @@ inline void DataTable(const char* strId, const DataTableOptions& option }); } - ++rowIdx; + ++renderState.rowIdx; }; + if constexpr (kSortable) { if (state.sortedIndices.size() != data.size()) @@ -311,11 +392,21 @@ inline void DataTable(const char* strId, const DataTableOptions& option } } + for (unsigned cnt = 0; cnt < renderState.currentTreeDepth; ++cnt) { + ImGui::TreePop(); + } + ImGui::EndTable(); } +template +void DataTable(const char* strId, const DataTableOptions& options, TData&& data, DataTableState& state, ImVec2 outerSize = {}) +{ + DataTableRenderState renderState; + DataTable(strId, options, std::forward(data), state, renderState, outerSize); +} template -inline DataTableColumn MakeStringColumn(const char* header, const char* TObject::* member) +DataTableColumn MakeStringColumn(const char* header, const char* TObject::* member) { return { .header = header, @@ -325,7 +416,7 @@ inline DataTableColumn MakeStringColumn(const char* header, const char* } template -inline DataTableColumn MakeStringColumn(const char* header, std::string TObject::* member) +DataTableColumn MakeStringColumn(const char* header, std::string TObject::* member) { return { .header = header, @@ -335,7 +426,7 @@ inline DataTableColumn MakeStringColumn(const char* header, std::string } template -inline DataTableColumn MakeColumn(const char* header, const char* fmt, TMember TObject::* member) +DataTableColumn MakeColumn(const char* header, const char* fmt, TMember TObject::* member) { return { .header = header, @@ -344,14 +435,193 @@ inline DataTableColumn MakeColumn(const char* header, const char* fmt, }; } -template -mijin::Task<> c_Window(const char* titleId, bool& open, ImGuiWindowFlags flags, TFunc renderFunc) +enum class WindowReopenMode { + APPEND, + DONT_OPEN, + FOCUS, + DUPLICATE, + CLOSE_EXISTING +}; + +struct WindowFlags : mijin::BitFlags +{ + bool topmost : 1 = false; + bool applicationTopmost : 1 = false; + bool noTaskbarIcon : 1 = false; +}; + +inline constexpr ImGuiWindowFlags kUnspecifiedWindowFlags = std::numeric_limits::max(); +struct WindowOptions +{ + ImGuiWindowFlags imguiFlags = kUnspecifiedWindowFlags; + ImVec2 initialSize = {-1.f, -1.f}; + WindowReopenMode reopenMode = WindowReopenMode::APPEND; + WindowFlags flags = {}; +}; + +inline constexpr ImGuiWindowFlags kDefaultWindowFlags = ImGuiWindowFlags_None; +inline constexpr ImGuiWindowFlags kDefaultPopupFlags = ImGuiWindowFlags_NoSavedSettings; +inline constexpr ImGuiWindowFlags kDefaultDialogFlags = ImGuiWindowFlags_NoSavedSettings; + +inline constexpr WindowOptions kDefaultPopupOptions = { + .imguiFlags = kDefaultPopupFlags, + .reopenMode = WindowReopenMode::CLOSE_EXISTING +}; + +inline constexpr WindowOptions kDefaultMessageBoxOptions = { + .imguiFlags = kDefaultDialogFlags | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize, + .reopenMode = WindowReopenMode::DUPLICATE, // TODO: this depends on the message box... + .flags = { + .applicationTopmost = true, + .noTaskbarIcon = true + } +}; + +namespace impl +{ +struct WindowState +{ + std::string newTitleId; // in case we want to duplicate the window if opened twice a new ID is automatically generated + int myInstanceID = 0; // used to close previous instances, if that is requested + bool showOnAppFocus = false; // for app topmost windows: has been hidden earlier and should be shown when the app regains focus +}; + +inline const ImGuiID kLastInstanceKey = ImHashStr("last_instance"); + +[[nodiscard]] +inline mijin::Task c_prepareOpenWindow(const char*& titleId, const WindowOptions& options, WindowState& state) +{ + if (options.reopenMode != WindowReopenMode::APPEND) + { + if (ImGuiWindow* window = ImGui::FindWindowByName(titleId); window != nullptr) + { + if (!window->WasActive) + { + // exists, but isn't active, reset the instance ID + window->StateStorage.SetInt(kLastInstanceKey, 0); + } + else + { + switch (options.reopenMode) + { + case WindowReopenMode::APPEND: + break; // how?5 + case WindowReopenMode::DONT_OPEN: + co_return false; + case WindowReopenMode::FOCUS: + ImGui::SetWindowFocus(titleId); + co_return false; + case WindowReopenMode::DUPLICATE: + state.newTitleId = std::format("{}##{}", titleId, mijin::getCurrentTask().getState()); + titleId = state.newTitleId.c_str(); + break; + case WindowReopenMode::CLOSE_EXISTING: + state.myInstanceID = window->StateStorage.GetInt(kLastInstanceKey) + 1; + window->StateStorage.SetInt(kLastInstanceKey, state.myInstanceID); + if (!(options.imguiFlags & ImGuiWindowFlags_NoFocusOnAppearing)) + { + // ImGui treats it as the same window, but it's actually a new one + ImGui::SetNextWindowFocus(); + } + + // wait one frame, otherwise it will be drawn twice, which breaks auto-sizing + co_await mijin::c_suspend(); + break; + } + } + } + } + + const ImGuiViewportFlags viewportFlags = + (options.flags.topmost ? ImGuiViewportFlags_TopMost : ImGuiViewportFlags_None) + | (options.flags.applicationTopmost ? ImGuiViewportFlags_NoAutoMerge : ImGuiViewportFlags_None) + | (options.flags.noTaskbarIcon ? ImGuiViewportFlags_NoTaskBarIcon : ImGuiViewportFlags_None); + if (viewportFlags != ImGuiViewportFlags_None) + { + ImGuiWindowClass windowClass; + windowClass.ViewportFlagsOverrideSet = viewportFlags; + ImGui::SetNextWindowClass(&windowClass); + } + + ImGui::SetNextWindowSize(options.initialSize); + co_return true; +} + +[[nodiscard]] +inline bool updateWindow(const WindowOptions& options, WindowState& state) +{ + const ImGuiWindow* const window = ImGui::GetCurrentWindow(); + if (window->StateStorage.GetInt(kLastInstanceKey) != state.myInstanceID) { + return false; + } + + if (options.flags.applicationTopmost && window->Viewport != nullptr && window->Viewport->PlatformHandle != nullptr) + { + const ImGuiWindow* popupModal = ImGui::GetTopMostPopupModal(); + const bool forceNoTopmost = (popupModal != nullptr && popupModal != window); // don't move to front if there is currently a modal popup + bool appHasFocus = false; + + for (ImGuiViewport* viewport : ImGui::GetPlatformIO().Viewports) + { + if (viewport->Flags & ImGuiViewportFlags_IsFocused) + { + appHasFocus = true; + break; + } + } + + const SDL_WindowID sdlID = static_cast(reinterpret_cast(window->Viewport->PlatformHandle)); + SDL_Window* sdlWindow = SDL_GetWindowFromID(sdlID); + + const SDL_WindowFlags sdlFlags = SDL_GetWindowFlags(sdlWindow); + const bool isTopmost = (sdlFlags & SDL_WINDOW_ALWAYS_ON_TOP); + const bool wantTopmost = (appHasFocus && !forceNoTopmost); + if (isTopmost != wantTopmost) + { + const bool isHidden = (sdlFlags & SDL_WINDOW_HIDDEN); + SDL_SetWindowAlwaysOnTop(sdlWindow, wantTopmost); + + // if we just removed the flag, it was topmost when the focused changed, so it's still on top + // minimize it to make up for that + if (!appHasFocus && !isHidden) + { + SDL_HideWindow(sdlWindow); + state.showOnAppFocus = true; + } + + // when regaining focus, check if we need to un-hide this window + if (appHasFocus && state.showOnAppFocus) + { + if (isHidden) { + SDL_ShowWindow(sdlWindow); + } + state.showOnAppFocus = false; + } + } + } + + return true; +} +} + +template +mijin::Task<> c_Window(const char* titleId, bool& open, const WindowOptions& options, TFunc renderFunc) +{ + impl::WindowState state; + if (!co_await impl::c_prepareOpenWindow(titleId, options, state)) { + co_return; + } + + const ImGuiWindowFlags flags = (options.imguiFlags != kUnspecifiedWindowFlags) ? options.imguiFlags : kDefaultWindowFlags; while (open) { if (ImGui::Begin(titleId, &open, flags)) { std::invoke(renderFunc); } + if (!impl::updateWindow(options, state)) { + open = false; + } ImGui::End(); co_await mijin::c_suspend(); } @@ -360,30 +630,68 @@ mijin::Task<> c_Window(const char* titleId, bool& open, ImGuiWindowFlags flags, template mijin::Task<> c_Window(const char* titleId, bool& open, TFunc renderFunc) { - return c_Window(titleId, open, ImGuiWindowFlags_None, std::move(renderFunc)); + co_return co_await c_Window(titleId, open, WindowOptions{}, std::move(renderFunc)); } template -mijin::Task<> c_Window(const char* titleId, ImGuiWindowFlags flags, TFunc renderFunc) +mijin::Task<> c_Window(const char* titleId, const WindowOptions& options, TFunc renderFunc) { bool open = true; - co_return co_await c_Window(titleId, open, flags, std::move(renderFunc)); + co_return co_await c_Window(titleId, open, options, std::move(renderFunc)); } template mijin::Task<> c_Window(const char* titleId, TFunc renderFunc) { - return c_Window(titleId, ImGuiWindowFlags_None, std::move(renderFunc)); + co_return co_await c_Window(titleId, WindowOptions{}, std::move(renderFunc)); } template -mijin::Task<> c_Dialog(const char* titleId, bool& open, ImGuiWindowFlags flags, TFunc renderFunc) +mijin::Task<> c_Popup(const char* titleId, const WindowOptions& options, TFunc renderFunc) { + impl::WindowState state; + if (!co_await impl::c_prepareOpenWindow(titleId, options, state)) { + co_return; + } + + const ImGuiWindowFlags flags = (options.imguiFlags != kUnspecifiedWindowFlags) ? options.imguiFlags : kDefaultPopupFlags; + bool open = true; + ImGui::OpenPopup(titleId); - - while (ImGui::BeginPopupModal(titleId, &open, flags)) + while (open && ImGui::BeginPopup(titleId, flags)) { std::invoke(renderFunc); + if (!impl::updateWindow(options, state)) { + open = false; + } + ImGui::EndPopup(); + co_await mijin::c_suspend(); + } +} + +template +mijin::Task<> c_Popup(const char* titleId, TFunc renderFunc) +{ + co_return co_await c_Popup(titleId, kDefaultPopupOptions, std::move(renderFunc)); +} + +template +mijin::Task<> c_Dialog(const char* titleId, bool& open, const WindowOptions& options, TFunc renderFunc) +{ + impl::WindowState state; + if (!co_await impl::c_prepareOpenWindow(titleId, options, state)) { + co_return; + } + + const ImGuiWindowFlags flags = (options.imguiFlags != kUnspecifiedWindowFlags) ? options.imguiFlags : kDefaultDialogFlags; + + ImGui::OpenPopup(titleId); + while (open && ImGui::BeginPopupModal(titleId, &open, flags)) + { + std::invoke(renderFunc); + if (!impl::updateWindow(options, state)) { + open = false; + } ImGui::EndPopup(); co_await mijin::c_suspend(); } @@ -392,38 +700,36 @@ mijin::Task<> c_Dialog(const char* titleId, bool& open, ImGuiWindowFlags flags, template mijin::Task<> c_Dialog(const char* titleId, bool& open, TFunc renderFunc) { - return c_Dialog(titleId, open, ImGuiWindowFlags_None, std::move(renderFunc)); + co_return co_await c_Dialog(titleId, open, kDefaultPopupOptions, std::move(renderFunc)); } template -mijin::Task<> c_Dialog(const char* titleId, ImGuiWindowFlags flags, TFunc renderFunc) +mijin::Task<> c_Dialog(const char* titleId, const WindowOptions& options, TFunc renderFunc) { bool open = true; - co_return co_await c_Dialog(titleId, open, flags, std::move(renderFunc)); + co_return co_await c_Dialog(titleId, open, options, std::move(renderFunc)); } template mijin::Task<> c_Dialog(const char* titleId, TFunc renderFunc) { - return c_Dialog(titleId, ImGuiWindowFlags_None, std::move(renderFunc)); + co_return co_await c_Dialog(titleId, WindowOptions{}, std::move(renderFunc)); +} + +template +mijin::Task<> c_MessageBox(const char* titleId, const WindowOptions& options, TFunc renderFunc) +{ + co_return co_await c_Dialog(titleId, options, std::move(renderFunc)); } template mijin::Task<> c_MessageBox(const char* titleId, TFunc renderFunc) { - ImGui::OpenPopup(titleId); - - bool open = true; - while (ImGui::BeginPopupModal(titleId, &open, ImGuiWindowFlags_NoResize)) - { - std::invoke(renderFunc); - ImGui::EndPopup(); - co_await mijin::c_suspend(); - } + return c_MessageBox(titleId, kDefaultMessageBoxOptions, std::move(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, const WindowOptions& options, std::format_string format, TFormatArgs&&... formatArgs) { const std::string message = std::format(format, std::forward(formatArgs)...); int buttonIdx = -1; @@ -435,8 +741,8 @@ mijin::Task c_MessageBox(const char* titleId, std::span } buttonsWidth += static_cast(buttons.size()) * 2.f * style.FramePadding.x;; buttonsWidth += static_cast(buttons.size() - 1) * style.ItemSpacing.x; - - co_await c_MessageBox(titleId, [&]() + + co_await c_MessageBox(titleId, options, [&]() { ImGui::TextUnformatted(message.c_str()); @@ -457,6 +763,12 @@ mijin::Task c_MessageBox(const char* titleId, std::span co_return buttonIdx; } +template +mijin::Task c_MessageBox(const char* titleId, std::span buttons, std::format_string format, TFormatArgs&&... formatArgs) +{ + return c_MessageBox(titleId, buttons, kDefaultMessageBoxOptions, format, std::forward(formatArgs)...); +} + struct YesNo { enum Value @@ -479,10 +791,10 @@ struct YesNo }; template -mijin::Task c_MessageBoxYesNo(const char* titleId, std::format_string format, TFormatArgs&&... formatArgs) +mijin::Task c_MessageBoxYesNo(const char* titleId, const WindowOptions& options, std::format_string format, TFormatArgs&&... formatArgs) { static const std::array BUTTONS = {"Yes", "No"}; - const int idx = co_await c_MessageBox(titleId, BUTTONS, format, std::forward(formatArgs)...); + const int idx = co_await c_MessageBox(titleId, BUTTONS, options, format, std::forward(formatArgs)...); switch (idx) { case 0: @@ -494,11 +806,35 @@ mijin::Task c_MessageBoxYesNo(const char* titleId, std::format_string +mijin::Task c_MessageBoxYesNo(const char* titleId, std::format_string format, TFormatArgs&&... formatArgs) +{ + return c_MessageBoxYesNo(titleId, kDefaultMessageBoxOptions, format, std::forward(formatArgs)...); +} + +template +mijin::Task<> c_MessageBoxText(const char* titleId, const WindowOptions& options, std::format_string format, TFormatArgs&&... formatArgs) +{ + static const char* const BUTTON_TEXT = "OK"; + co_await c_MessageBox(titleId, {&BUTTON_TEXT, 1}, options, format, std::forward(formatArgs)...); +} + template 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)...); + return c_MessageBoxText(titleId, kDefaultMessageBoxOptions, format, std::forward(formatArgs)...); +} + +template +void InputScalar(const char* label, T* value, T step = T(0), T stepFast = T(100), ImGuiInputTextFlags flags = 0) +{ + ImGui::InputScalar(label, raid::kImGuiDataType, value, &step, &stepFast, /* format = */ nullptr, flags); +} + +template +void InputScalar(const char* label, T* value, T step = T(0), T stepFast = T(0), const char* format = "%.3f", ImGuiInputTextFlags flags = 0) +{ + ImGui::InputScalar(label, raid::kImGuiDataType, value, &step, &stepFast, format, flags); } } // namespace ImRaid diff --git a/public/raid/imutil.hpp b/public/raid/imutil.hpp new file mode 100644 index 0000000..dfd7ff3 --- /dev/null +++ b/public/raid/imutil.hpp @@ -0,0 +1,57 @@ + +#pragma once + +#if !defined(RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED) +#define RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED 1 + +#include +#include +#include +#include + +namespace raid +{ + +namespace impl +{ +template +inline std::string gFormatBuffer; + +template +struct imgui_data_type; + +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +template<> struct imgui_data_type : std::integral_constant {}; +} + +template +[[nodiscard]] +const char* formatTemp(std::string& formatBuffer, std::format_string fmt, TArgs&&... args) +{ + formatBuffer.clear(); + std::format_to(std::back_inserter(formatBuffer), fmt, std::forward(args)...); + return formatBuffer.c_str(); +} + +template +[[nodiscard]] +const char* formatTemp(std::format_string fmt, TArgs&&... args) +{ + return formatTemp(impl::gFormatBuffer, fmt, std::forward(args)...); +} + +template +inline constexpr ImGuiDataType kImGuiDataType = impl::imgui_data_type::value; +} + +#endif