Added imutil.hpp/cpp for ImGui utility functions.
This commit is contained in:
parent
4228fe554d
commit
f5aa085da9
@ -9,6 +9,7 @@ if not hasattr(env, 'Jinja'):
|
||||
src_files = Split("""
|
||||
application.cpp
|
||||
config.cpp
|
||||
imutil.cpp
|
||||
fonts.gen.cpp
|
||||
stb_image.cpp
|
||||
""")
|
||||
|
||||
7
private/raid/imutil.cpp
Normal file
7
private/raid/imutil.cpp
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
#include "raid/imutil.hpp"
|
||||
|
||||
namespace raid::impl
|
||||
{
|
||||
// std::string gFormatBuffer;
|
||||
}
|
||||
@ -16,8 +16,13 @@
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <mijin/async/coroutine.hpp>
|
||||
#include <mijin/debug/assert.hpp>
|
||||
#include <mijin/util/bitflags.hpp>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#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<typename TObject>
|
||||
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<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 getstate_t = std::function<DataTableItemState(const TObject&)>;
|
||||
using select_handler_t = std::function<void(const TObject&, SelectAction)>;
|
||||
using gettreeitem_t = std::function<DataTableTreeItem(const TObject&)>;
|
||||
|
||||
std::span<const DataTableColumn<TObject>> 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<typename TObject, typename TData>
|
||||
inline void DataTable(const char* strId, const DataTableOptions<TObject>& options, TData&& data, DataTableState& state, ImVec2 outerSize = {})
|
||||
inline void DataTable(const char* strId, const DataTableOptions<TObject>& options, TData&& data, DataTableState& state, DataTableRenderState<TObject>& 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<TObject>& option
|
||||
}
|
||||
ImGui::TableSetupColumn(column.header, flags, column.initialWidthOrWeight);
|
||||
}
|
||||
|
||||
if (!options.noHeaders)
|
||||
{
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableHeadersRow();
|
||||
}
|
||||
|
||||
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
|
||||
if (sortSpecs != nullptr && sortSpecs->SpecsDirty)
|
||||
@ -228,34 +269,73 @@ inline void DataTable(const char* strId, const DataTableOptions<TObject>& option
|
||||
};
|
||||
}
|
||||
|
||||
std::size_t rowIdx = 0;
|
||||
typename DataTableOptions<TObject>::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<TObject>& 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<TObject>& 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<TObject>& option
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned cnt = 0; cnt < renderState.currentTreeDepth; ++cnt) {
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
template<typename TObject, typename TData>
|
||||
void DataTable(const char* strId, const DataTableOptions<TObject>& options, TData&& data, DataTableState& state, ImVec2 outerSize = {})
|
||||
{
|
||||
DataTableRenderState<TObject> renderState;
|
||||
DataTable(strId, options, std::forward<TData>(data), state, renderState, outerSize);
|
||||
}
|
||||
|
||||
template<typename TObject>
|
||||
inline DataTableColumn<TObject> MakeStringColumn(const char* header, const char* TObject::* member)
|
||||
DataTableColumn<TObject> MakeStringColumn(const char* header, const char* TObject::* member)
|
||||
{
|
||||
return {
|
||||
.header = header,
|
||||
@ -325,7 +416,7 @@ inline DataTableColumn<TObject> MakeStringColumn(const char* header, const char*
|
||||
}
|
||||
|
||||
template<typename TObject>
|
||||
inline DataTableColumn<TObject> MakeStringColumn(const char* header, std::string TObject::* member)
|
||||
DataTableColumn<TObject> MakeStringColumn(const char* header, std::string TObject::* member)
|
||||
{
|
||||
return {
|
||||
.header = header,
|
||||
@ -335,7 +426,7 @@ inline DataTableColumn<TObject> MakeStringColumn(const char* header, std::string
|
||||
}
|
||||
|
||||
template<typename TObject, typename TMember>
|
||||
inline DataTableColumn<TObject> MakeColumn(const char* header, const char* fmt, TMember TObject::* member)
|
||||
DataTableColumn<TObject> MakeColumn(const char* header, const char* fmt, TMember TObject::* member)
|
||||
{
|
||||
return {
|
||||
.header = header,
|
||||
@ -344,14 +435,193 @@ inline DataTableColumn<TObject> MakeColumn(const char* header, const char* fmt,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
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<WindowFlags>
|
||||
{
|
||||
bool topmost : 1 = false;
|
||||
bool applicationTopmost : 1 = false;
|
||||
bool noTaskbarIcon : 1 = false;
|
||||
};
|
||||
|
||||
inline constexpr ImGuiWindowFlags kUnspecifiedWindowFlags = std::numeric_limits<ImGuiWindowFlags>::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<bool> 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<SDL_WindowID>(reinterpret_cast<std::uintptr_t>(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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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)
|
||||
{
|
||||
ImGui::OpenPopup(titleId);
|
||||
impl::WindowState state;
|
||||
if (!co_await impl::c_prepareOpenWindow(titleId, options, state)) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
while (ImGui::BeginPopupModal(titleId, &open, flags))
|
||||
const ImGuiWindowFlags flags = (options.imguiFlags != kUnspecifiedWindowFlags) ? options.imguiFlags : kDefaultPopupFlags;
|
||||
bool open = true;
|
||||
|
||||
ImGui::OpenPopup(titleId);
|
||||
while (open && ImGui::BeginPopup(titleId, flags))
|
||||
{
|
||||
std::invoke(renderFunc);
|
||||
if (!impl::updateWindow(options, state)) {
|
||||
open = false;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
co_await mijin::c_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
mijin::Task<> c_Popup(const char* titleId, TFunc renderFunc)
|
||||
{
|
||||
co_return co_await c_Popup(titleId, kDefaultPopupOptions, std::move(renderFunc));
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
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<typename TFunc>
|
||||
mijin::Task<> c_MessageBox(const char* titleId, const WindowOptions& options, TFunc renderFunc)
|
||||
{
|
||||
co_return co_await c_Dialog(titleId, options, std::move(renderFunc));
|
||||
}
|
||||
|
||||
template<typename TFunc>
|
||||
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<typename... TFormatArgs>
|
||||
mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char* const> buttons, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char* const> buttons, const WindowOptions& options, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
const std::string message = std::format(format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
int buttonIdx = -1;
|
||||
@ -436,7 +742,7 @@ mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char* const>
|
||||
buttonsWidth += static_cast<float>(buttons.size()) * 2.f * style.FramePadding.x;;
|
||||
buttonsWidth += static_cast<float>(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<int> c_MessageBox(const char* titleId, std::span<const char* const>
|
||||
co_return buttonIdx;
|
||||
}
|
||||
|
||||
template<typename... TFormatArgs>
|
||||
mijin::Task<int> c_MessageBox(const char* titleId, std::span<const char* const> buttons, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
return c_MessageBox(titleId, buttons, kDefaultMessageBoxOptions, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
}
|
||||
|
||||
struct YesNo
|
||||
{
|
||||
enum Value
|
||||
@ -479,10 +791,10 @@ 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)
|
||||
mijin::Task<YesNo> c_MessageBoxYesNo(const char* titleId, const WindowOptions& options, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
static const std::array<const char*, 2> BUTTONS = {"Yes", "No"};
|
||||
const int idx = co_await c_MessageBox(titleId, BUTTONS, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
const int idx = co_await c_MessageBox(titleId, BUTTONS, options, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
switch (idx)
|
||||
{
|
||||
case 0:
|
||||
@ -494,11 +806,35 @@ mijin::Task<YesNo> c_MessageBoxYesNo(const char* titleId, std::format_string<TFo
|
||||
}
|
||||
}
|
||||
|
||||
template<YesNo::Value DefaultResult = YesNo::NO, typename... TFormatArgs>
|
||||
mijin::Task<YesNo> c_MessageBoxYesNo(const char* titleId, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
return c_MessageBoxYesNo(titleId, kDefaultMessageBoxOptions, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
}
|
||||
|
||||
template<typename... TFormatArgs>
|
||||
mijin::Task<> c_MessageBoxText(const char* titleId, const WindowOptions& options, std::format_string<TFormatArgs...> format, TFormatArgs&&... formatArgs)
|
||||
{
|
||||
static const char* const BUTTON_TEXT = "OK";
|
||||
co_await c_MessageBox(titleId, {&BUTTON_TEXT, 1}, options, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
}
|
||||
|
||||
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)...);
|
||||
return c_MessageBoxText(titleId, kDefaultMessageBoxOptions, format, std::forward<TFormatArgs>(formatArgs)...);
|
||||
}
|
||||
|
||||
template<std::integral T>
|
||||
void InputScalar(const char* label, T* value, T step = T(0), T stepFast = T(100), ImGuiInputTextFlags flags = 0)
|
||||
{
|
||||
ImGui::InputScalar(label, raid::kImGuiDataType<T>, value, &step, &stepFast, /* format = */ nullptr, flags);
|
||||
}
|
||||
|
||||
template<std::floating_point T>
|
||||
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<T>, value, &step, &stepFast, format, flags);
|
||||
}
|
||||
} // namespace ImRaid
|
||||
|
||||
|
||||
57
public/raid/imutil.hpp
Normal file
57
public/raid/imutil.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED)
|
||||
#define RAID_PUBLIC_RAID_IMUTIL_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <string>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace raid
|
||||
{
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<std::size_t I>
|
||||
inline std::string gFormatBuffer;
|
||||
|
||||
template<typename T>
|
||||
struct imgui_data_type;
|
||||
|
||||
template<> struct imgui_data_type<std::int8_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_S8> {};
|
||||
template<> struct imgui_data_type<std::uint8_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_U8> {};
|
||||
template<> struct imgui_data_type<std::int16_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_S16> {};
|
||||
template<> struct imgui_data_type<std::uint16_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_U16> {};
|
||||
template<> struct imgui_data_type<std::int32_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_S32> {};
|
||||
template<> struct imgui_data_type<std::uint32_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_U32> {};
|
||||
template<> struct imgui_data_type<std::int64_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_S64> {};
|
||||
template<> struct imgui_data_type<std::uint64_t> : std::integral_constant<ImGuiDataType, ImGuiDataType_U64> {};
|
||||
template<> struct imgui_data_type<float> : std::integral_constant<ImGuiDataType, ImGuiDataType_Float> {};
|
||||
template<> struct imgui_data_type<double> : std::integral_constant<ImGuiDataType, ImGuiDataType_Double> {};
|
||||
template<> struct imgui_data_type<bool> : std::integral_constant<ImGuiDataType, ImGuiDataType_Bool> {};
|
||||
template<> struct imgui_data_type<const char*> : std::integral_constant<ImGuiDataType, ImGuiDataType_String> {};
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
[[nodiscard]]
|
||||
const char* formatTemp(std::string& formatBuffer, std::format_string<TArgs...> fmt, TArgs&&... args)
|
||||
{
|
||||
formatBuffer.clear();
|
||||
std::format_to(std::back_inserter(formatBuffer), fmt, std::forward<TArgs>(args)...);
|
||||
return formatBuffer.c_str();
|
||||
}
|
||||
|
||||
template<std::size_t I = 0, typename... TArgs>
|
||||
[[nodiscard]]
|
||||
const char* formatTemp(std::format_string<TArgs...> fmt, TArgs&&... args)
|
||||
{
|
||||
return formatTemp(impl::gFormatBuffer<I>, fmt, std::forward<TArgs>(args)...);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline constexpr ImGuiDataType kImGuiDataType = impl::imgui_data_type<T>::value;
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user