From f5aa085da9682c5c48e1c2d908e04fcb8096cbc2 Mon Sep 17 00:00:00 2001
From: Patrick Wuttke
Date: Sat, 22 Nov 2025 12:45:14 +0100
Subject: [PATCH] Added imutil.hpp/cpp for ImGui utility functions.
---
private/raid/SModule | 1 +
private/raid/imutil.cpp | 7 +
public/raid/imraid.hpp | 432 +++++++++++++++++++++++++++++++++++-----
public/raid/imutil.hpp | 57 ++++++
4 files changed, 449 insertions(+), 48 deletions(-)
create mode 100644 private/raid/imutil.cpp
create mode 100644 public/raid/imutil.hpp
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