diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp index a0cb9e405..da2e2183d 100644 --- a/src/core/libraries/system/msgdialog.cpp +++ b/src/core/libraries/system/msgdialog.cpp @@ -3,6 +3,7 @@ #include #include + #include "common/assert.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" @@ -10,11 +11,10 @@ #include "core/libraries/system/msgdialog.h" #include "imgui/imgui_layer.h" #include "imgui/imgui_std.h" +#include "imgui_internal.h" namespace Libraries::MsgDialog { -class MsgDialogGui; - using CommonDialog::Error; using CommonDialog::Result; using CommonDialog::Status; @@ -102,26 +102,88 @@ struct Param { struct MsgDialogResult { FixedValue mode{}; - Result result{}; - ButtonId buttonId{}; + Result result{Result::OK}; + ButtonId buttonId{ButtonId::INVALID}; std::array reserved{}; }; -class MsgDialogGui final : public ImGui::Layer { +static auto g_status = Status::NONE; +static MsgDialogResult g_result{}; + +struct { + int count = 0; + const char* text1; + const char* text2; +} static constexpr button_texts[] = { + {1, "OK"}, // 0 OK + {2, "Yes", "No"}, // 1 YESNO + {0}, // 2 NONE + {2, "OK", "Cancel"}, // 3 OK_CANCEL + {}, // 4 !!NOP + {1, "Wait"}, // 5 WAIT + {2, "Wait", "Cancel"}, // 6 WAIT_CANCEL + {2, "Yes", "No"}, // 7 YESNO_FOCUS_NO + {2, "OK", "Cancel"}, // 8 OK_CANCEL_FOCUS_CANCEL + {0xFF}, // 9 TWO_BUTTONS +}; +static_assert(std::size(button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1); + +static void Finish(ButtonId buttonId); + +namespace { +using namespace ImGui; +class MsgDialogGui final : public Layer { const Param* param{}; + s32 dialog_unique_id{}; void DrawUser(const UserMessageParam& user_message_param) { - const auto ws = ImGui::GetWindowSize(); - ImGui::SetCursorPosY(ws.y / 2.0f - 30.0f); - ImGui::BeginGroup(); - ImGui::PushTextWrapPos(ws.x - 30.0f); - ImGui::SetCursorPosX( - (ws.x - ImGui::CalcTextSize(user_message_param.msg, nullptr, false, ws.x - 40.0f).x) / - 2.0f); - ImGui::Text("%s", user_message_param.msg); - ImGui::PopTextWrapPos(); - ImGui::EndGroup(); - switch (user_message_param.buttonType) {} + constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; + + const auto ws = GetWindowSize(); + SetCursorPosY(ws.y / 2.0f - 50.0f); + PushTextWrapPos(ws.x - 30.0f); + SetCursorPosX( + (ws.x - CalcTextSize(user_message_param.msg, nullptr, false, ws.x - 40.0f).x) / 2.0f); + Text("%s", user_message_param.msg); + PopTextWrapPos(); + const auto button_type = user_message_param.buttonType; + ASSERT(user_message_param.buttonType <= ButtonType::TWO_BUTTONS); + auto [count, text1, text2] = button_texts[static_cast(button_type)]; + if (count == 0xFF) { // TWO_BUTTONS -> User defined message + count = 2; + text1 = user_message_param.buttonsParam->msg1; + text2 = user_message_param.buttonsParam->msg2; + } + const bool focus_first = button_type < ButtonType::YESNO_FOCUS_NO; + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - 10.0f - BUTTON_SIZE.y, + }); + BeginGroup(); + if (count > 0) { + // First button at the right, so we render the second button first + if (count == 2) { + PushID(2); + if (Button(text2, BUTTON_SIZE)) { + Finish(ButtonId::BUTTON2); + } + if (!focus_first) { + SetItemDefaultNav(); + } + PopID(); + SameLine(); + } + PushID(1); + if (Button(text1, BUTTON_SIZE)) { + Finish(ButtonId::BUTTON1); + } + if (focus_first) { + SetItemDefaultNav(); + } + PopID(); + SameLine(); + } + EndGroup(); } void DrawProgressBar(const ProgressBarParam& progress_bar_param) {} @@ -130,46 +192,67 @@ class MsgDialogGui final : public ImGui::Layer { public: void Draw() override { - const auto& io = ImGui::GetIO(); + if (g_status != Status::RUNNING) { + return; + } + const auto& io = GetIO(); const ImVec2 window_size{ std::min(io.DisplaySize.x, 500.0f), std::min(io.DisplaySize.y, 300.0f), }; - ImGui::CentralizeWindow(); - ImGui::SetNextWindowSize(window_size); - ImGui::Begin("##Message Dialog", nullptr, ImGuiWindowFlags_NoDecoration); - switch (param->mode) { - case MsgDialogMode::USER_MSG: - DrawUser(*param->userMsgParam); - break; - case MsgDialogMode::PROGRESS_BAR: - DrawProgressBar(*param->progBarParam); - break; - case MsgDialogMode::SYSTEM_MSG: - DrawSystemMessage(*param->sysMsgParam); - break; + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowFocus(); + SetNextWindowCollapsed(false); + KeepNavHighlight(); + // Hack to allow every dialog to have a unique window +#define TITLE "Message Dialog##MD" + char id[sizeof(TITLE) + sizeof(int)] = TITLE "\0\0\0\0"; + *reinterpret_cast(&id[sizeof(TITLE) - 1]) = + dialog_unique_id | + 0x80808080; // keep the MSB set to extend the string length (null terminated) +#undef TITLE + if (Begin(id, nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + switch (param->mode) { + case MsgDialogMode::USER_MSG: + DrawUser(*param->userMsgParam); + break; + case MsgDialogMode::PROGRESS_BAR: + DrawProgressBar(*param->progBarParam); + break; + case MsgDialogMode::SYSTEM_MSG: + DrawSystemMessage(*param->sysMsgParam); + break; + } + End(); } - ImGui::End(); } bool ShouldGrabGamepad() override { return true; } - explicit MsgDialogGui(const Param* param = nullptr) : param(param) {} -} static g_msg_dialog_gui; + explicit MsgDialogGui(const Param* param = nullptr) + : param(param), dialog_unique_id([] { + static s32 last_id = 0; + return last_id++; + }()) {} +} g_msg_dialog_gui; +} // namespace -static auto g_status = Status::NONE; -static MsgDialogResult g_result{}; +static void Finish(ButtonId buttonId) { + g_result.buttonId = buttonId; + g_status = Status::FINISHED; + Layer::RemoveLayer(&g_msg_dialog_gui); +} Error PS4_SYSV_ABI sceMsgDialogClose() { if (g_status != Status::RUNNING) { return Error::NOT_RUNNING; } - g_status = Status::FINISHED; - ImGui::Layer::RemoveLayer(&g_msg_dialog_gui); + Finish(ButtonId::INVALID); return Error::OK; } @@ -189,7 +272,7 @@ Error PS4_SYSV_ABI sceMsgDialogGetResult(MsgDialogResult* result) { return Error::OK; } -CommonDialog::Status PS4_SYSV_ABI sceMsgDialogGetStatus() { +Status PS4_SYSV_ABI sceMsgDialogGetStatus() { return g_status; } @@ -216,8 +299,9 @@ Error PS4_SYSV_ABI sceMsgDialogOpen(const Param* param) { ASSERT(param->size == sizeof(Param)); ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); g_status = Status::RUNNING; + g_result = {}; g_msg_dialog_gui = MsgDialogGui(param); - ImGui::Layer::AddLayer(&g_msg_dialog_gui); + Layer::AddLayer(&g_msg_dialog_gui); return Error::OK; } diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index 5f577c036..496de0e0a 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -5,10 +5,25 @@ #include +#include "imgui_internal.h" + namespace ImGui { inline void CentralizeWindow() { const auto display_size = GetIO().DisplaySize; SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); } + +inline void KeepNavHighlight() { + GetCurrentContext()->NavDisableHighlight = false; +} + +inline void SetItemDefaultNav() { + if (IsWindowAppearing()) { + const auto ctx = GetCurrentContext(); + SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow); + ctx->NavInitResult.Clear(); + } +} + } // namespace ImGui