Message Dialog library (#767)

* system/MsgDialog: types & basic text display

* system/MsgDialog: User message dialog

* system/MsgDialog: RAII for MsgDialog ui

* system/MsgDialog: Progress bar dialog

* system/MsgDialog: System message texts

* system/MsgDialog: copy all ui state to local memory

handles when game release memory before close
extracted all UI code to it's own file
use single window instead of creating new one every single dialogOpen

* system/MsgDialog: debug logging
This commit is contained in:
Vinicius Rangel 2024-09-08 17:27:50 -03:00 committed by GitHub
parent 035cb3eeaa
commit 446d8c4ff1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 716 additions and 153 deletions

View file

@ -204,6 +204,7 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/system/commondialog.h
src/core/libraries/system/msgdialog.cpp
src/core/libraries/system/msgdialog.h
src/core/libraries/system/msgdialog_ui.cpp
src/core/libraries/system/posix.cpp
src/core/libraries/system/posix.h
src/core/libraries/save_data/error_codes.h
@ -325,6 +326,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/error.cpp
src/common/error.h
src/common/scope_exit.h
src/common/fixed_value.h
src/common/func_traits.h
src/common/native_clock.cpp
src/common/native_clock.h
@ -563,6 +565,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
set(IMGUI src/imgui/imgui_config.h
src/imgui/imgui_layer.h
src/imgui/imgui_std.h
src/imgui/layer/video_info.cpp
src/imgui/layer/video_info.h
src/imgui/renderer/imgui_core.cpp

35
src/common/fixed_value.h Normal file
View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/**
* @brief A template class that encapsulates a fixed, compile-time constant value.
*
* @tparam T The type of the value.
* @tparam Value The fixed value of type T.
*
* This class provides a way to encapsulate a value that is constant and known at compile-time.
* The value is stored as a private member and cannot be changed. Any attempt to assign a new
* value to an object of this class will reset it to the fixed value.
*/
template <typename T, T Value>
class FixedValue {
T m_value{Value};
public:
constexpr FixedValue() = default;
constexpr explicit(false) operator T() const {
return m_value;
}
FixedValue& operator=(const T&) {
m_value = Value;
return *this;
}
FixedValue& operator=(T&&) noexcept {
m_value = {Value};
return *this;
}
};

View file

@ -8,6 +8,9 @@
namespace Libraries::CommonDialog {
bool g_isInitialized = false;
bool g_isUsed = false;
int PS4_SYSV_ABI _ZN3sce16CommonDialogUtil12getSelfAppIdEv() {
LOG_ERROR(Lib_CommonDlg, "(STUBBED) called");
return ORBIS_OK;
@ -83,14 +86,19 @@ int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceCommonDialogInitialize() {
LOG_ERROR(Lib_CommonDlg, "(DUMMY) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceCommonDialogInitialize() {
if (g_isInitialized) {
LOG_INFO(Lib_CommonDlg, "already initialized");
return Error::ALREADY_SYSTEM_INITIALIZED;
}
LOG_DEBUG(Lib_CommonDlg, "initialized");
g_isInitialized = true;
return Error::OK;
}
int PS4_SYSV_ABI sceCommonDialogIsUsed() {
LOG_ERROR(Lib_CommonDlg, "(STUBBED) called");
return ORBIS_OK;
bool PS4_SYSV_ABI sceCommonDialogIsUsed() {
LOG_TRACE(Lib_CommonDlg, "called");
return g_isUsed;
}
int PS4_SYSV_ABI Func_0FF577E4E8457883() {

View file

@ -11,9 +11,44 @@ class SymbolsResolver;
namespace Libraries::CommonDialog {
struct OrbisCommonDialogBaseParam {
enum class Status : u32 {
NONE = 0,
INITIALIZED = 1,
RUNNING = 2,
FINISHED = 3,
};
enum class Result : u32 {
OK = 0,
USER_CANCELED = 1,
};
enum class Error : u32 {
OK = 0,
NOT_SYSTEM_INITIALIZED = 0x80B80001,
ALREADY_SYSTEM_INITIALIZED = 0x80B80002,
NOT_INITIALIZED = 0x80B80003,
ALREADY_INITIALIZED = 0x80B80004,
NOT_FINISHED = 0x80B80005,
INVALID_STATE = 0x80B80006,
RESULT_NONE = 0x80B80007,
BUSY = 0x80B80008,
OUT_OF_MEMORY = 0x80B80009,
PARAM_INVALID = 0x80B8000A,
NOT_RUNNING = 0x80B8000B,
ALREADY_CLOSE = 0x80B8000C,
ARG_NULL = 0x80B8000D,
UNEXPECTED_FATAL = 0x80B8000E,
NOT_SUPPORTED = 0x80B8000F,
INHIBIT_SHAREPLAY_CLIENT = 0x80B80010,
};
extern bool g_isInitialized;
extern bool g_isUsed;
struct BaseParam {
std::size_t size;
u8 reserved[36];
std::array<u8, 36> reserved;
u32 magic;
};
@ -32,8 +67,8 @@ int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8getAppIdEv();
int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client8isFinishEv();
int PS4_SYSV_ABI _ZNK3sce16CommonDialogUtil6Client9getResultEv();
int PS4_SYSV_ABI _ZTVN3sce16CommonDialogUtil6ClientE();
int PS4_SYSV_ABI sceCommonDialogInitialize();
int PS4_SYSV_ABI sceCommonDialogIsUsed();
Error PS4_SYSV_ABI sceCommonDialogInitialize();
bool PS4_SYSV_ABI sceCommonDialogIsUsed();
int PS4_SYSV_ABI Func_0FF577E4E8457883();
int PS4_SYSV_ABI Func_41716C2CE379416C();
int PS4_SYSV_ABI Func_483A427D8F6E0748();

View file

@ -1,79 +1,157 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <imgui.h>
#include <magic_enum.hpp>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/system/msgdialog.h"
#include <magic_enum.hpp>
#include "imgui_internal.h"
#include "msgdialog_ui.h"
namespace Libraries::MsgDialog {
int PS4_SYSV_ABI sceMsgDialogClose() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
}
using CommonDialog::Error;
using CommonDialog::Result;
using CommonDialog::Status;
int PS4_SYSV_ABI sceMsgDialogGetResult() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
}
static auto g_status = Status::NONE;
static MsgDialogState g_state{};
static DialogResult g_result{};
static MsgDialogUi g_msg_dialog_ui;
int PS4_SYSV_ABI sceMsgDialogGetStatus() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceMsgDialogInitialize() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param) {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
switch (param->mode) {
case ORBIS_MSG_DIALOG_MODE_USER_MSG:
LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen userMsg type = %s msg = %s",
magic_enum::enum_name(param->userMsgParam->buttonType), param->userMsgParam->msg);
break;
case ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR:
LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen progressBar type = %s msg = %s",
magic_enum::enum_name(param->progBarParam->barType), param->progBarParam->msg);
break;
case ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG:
LOG_INFO(Lib_MsgDlg, "sceMsgDialogOpen systemMsg type: %s",
magic_enum::enum_name(param->sysMsgParam->sysMsgType));
break;
default:
break;
Error PS4_SYSV_ABI sceMsgDialogClose() {
LOG_DEBUG(Lib_MsgDlg, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
return ORBIS_OK;
g_msg_dialog_ui.Finish(ButtonId::INVALID);
return Error::OK;
}
int PS4_SYSV_ABI sceMsgDialogProgressBarInc() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) {
LOG_DEBUG(Lib_MsgDlg, "called");
if (g_status != Status::FINISHED) {
return Error::NOT_FINISHED;
}
if (result == nullptr) {
return Error::ARG_NULL;
}
for (const auto v : result->reserved) {
if (v != 0) {
return Error::PARAM_INVALID;
}
}
*result = g_result;
return Error::OK;
}
int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
Status PS4_SYSV_ABI sceMsgDialogGetStatus() {
LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceMsgDialogInitialize() {
LOG_DEBUG(Lib_MsgDlg, "called");
if (!CommonDialog::g_isInitialized) {
return Error::NOT_SYSTEM_INITIALIZED;
}
if (g_status != Status::NONE) {
return Error::ALREADY_INITIALIZED;
}
if (CommonDialog::g_isUsed) {
return Error::BUSY;
}
g_status = Status::INITIALIZED;
CommonDialog::g_isUsed = true;
return Error::OK;
}
int PS4_SYSV_ABI sceMsgDialogTerminate() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param) {
if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
LOG_INFO(Lib_MsgDlg, "called without initialize");
return Error::INVALID_STATE;
}
if (param == nullptr) {
LOG_DEBUG(Lib_MsgDlg, "called param:(NULL)");
return Error::ARG_NULL;
}
LOG_DEBUG(Lib_MsgDlg, "called param->mode: {}", magic_enum::enum_name(param->mode));
ASSERT(param->size == sizeof(OrbisParam));
ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam));
g_result = {};
g_state = MsgDialogState{*param};
g_status = Status::RUNNING;
g_msg_dialog_ui = MsgDialogUi(&g_state, &g_status, &g_result);
return Error::OK;
}
int PS4_SYSV_ABI sceMsgDialogUpdateStatus() {
LOG_ERROR(Lib_MsgDlg, "(STUBBED) called");
return ORBIS_OK;
Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget target, u32 delta) {
LOG_DEBUG(Lib_MsgDlg, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<MsgDialogState::ProgressState>().progress += delta;
return Error::OK;
}
Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget target,
const char* msg) {
LOG_DEBUG(Lib_MsgDlg, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<MsgDialogState::ProgressState>().msg = msg;
return Error::OK;
}
Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget target,
u32 value) {
LOG_DEBUG(Lib_MsgDlg, "called");
if (g_status != Status::RUNNING) {
return Error::NOT_RUNNING;
}
if (g_state.GetMode() != MsgDialogMode::PROGRESS_BAR) {
return Error::NOT_SUPPORTED;
}
if (target != OrbisMsgDialogProgressBarTarget::DEFAULT) {
return Error::PARAM_INVALID;
}
g_state.GetState<MsgDialogState::ProgressState>().progress = value;
return Error::OK;
}
Error PS4_SYSV_ABI sceMsgDialogTerminate() {
LOG_DEBUG(Lib_MsgDlg, "called");
if (g_status == Status::RUNNING) {
sceMsgDialogClose();
}
if (g_status == Status::NONE) {
return Error::NOT_INITIALIZED;
}
g_status = Status::NONE;
CommonDialog::g_isUsed = false;
return Error::OK;
}
Status PS4_SYSV_ABI sceMsgDialogUpdateStatus() {
LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
return g_status;
}
void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym) {

View file

@ -3,7 +3,6 @@
#pragma once
#include "common/types.h"
#include "core/libraries/system/commondialog.h"
namespace Core::Loader {
@ -12,95 +11,23 @@ class SymbolsResolver;
namespace Libraries::MsgDialog {
using OrbisUserServiceUserId = s32;
struct DialogResult;
struct OrbisParam;
enum class OrbisMsgDialogProgressBarTarget : u32;
enum OrbisCommonDialogStatus {
ORBIS_COMMON_DIALOG_STATUS_NONE = 0,
ORBIS_COMMON_DIALOG_STATUS_INITIALIZED = 1,
ORBIS_COMMON_DIALOG_STATUS_RUNNING = 2,
ORBIS_COMMON_DIALOG_STATUS_FINISHED = 3
};
enum OrbisMsgDialogMode {
ORBIS_MSG_DIALOG_MODE_USER_MSG = 1,
ORBIS_MSG_DIALOG_MODE_PROGRESS_BAR = 2,
ORBIS_MSG_DIALOG_MODE_SYSTEM_MSG = 3,
};
enum OrbisMsgDialogButtonType {
ORBIS_MSG_DIALOG_BUTTON_TYPE_OK = 0,
ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO = 1,
ORBIS_MSG_DIALOG_BUTTON_TYPE_NONE = 2,
ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL = 3,
ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT = 5,
ORBIS_MSG_DIALOG_BUTTON_TYPE_WAIT_CANCEL = 6,
ORBIS_MSG_DIALOG_BUTTON_TYPE_YESNO_FOCUS_NO = 7,
ORBIS_MSG_DIALOG_BUTTON_TYPE_OK_CANCEL_FOCUS_CANCEL = 8,
ORBIS_MSG_DIALOG_BUTTON_TYPE_2BUTTONS = 9,
};
enum OrbisMsgDialogProgressBarType {
ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE = 0,
ORBIS_MSG_DIALOG_PROGRESSBAR_TYPE_PERCENTAGE_CANCEL = 1,
};
enum OrbisMsgDialogSystemMessageType {
ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_EMPTY_STORE = 0,
ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_CHAT_RESTRICTION = 1,
ORBIS_MSG_DIALOG_SYSMSG_TYPE_TRC_PSN_UGC_RESTRICTION = 2,
ORBIS_MSG_DIALOG_SYSMSG_TYPE_CAMERA_NOT_CONNECTED = 4,
ORBIS_MSG_DIALOG_SYSMSG_TYPE_WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5,
};
struct OrbisMsgDialogButtonsParam {
const char* msg1;
const char* msg2;
char reserved[32];
};
struct OrbisMsgDialogUserMessageParam {
OrbisMsgDialogButtonType buttonType;
s32 : 32;
const char* msg;
OrbisMsgDialogButtonsParam* buttonsParam;
char reserved[24];
};
struct OrbisMsgDialogProgressBarParam {
OrbisMsgDialogProgressBarType barType;
int32_t : 32;
const char* msg;
char reserved[64];
};
struct OrbisMsgDialogSystemMessageParam {
OrbisMsgDialogSystemMessageType sysMsgType;
char reserved[32];
};
struct OrbisMsgDialogParam {
CommonDialog::OrbisCommonDialogBaseParam baseParam;
std::size_t size;
OrbisMsgDialogMode mode;
s32 : 32;
OrbisMsgDialogUserMessageParam* userMsgParam;
OrbisMsgDialogProgressBarParam* progBarParam;
OrbisMsgDialogSystemMessageParam* sysMsgParam;
OrbisUserServiceUserId userId;
char reserved[40];
s32 : 32;
};
int PS4_SYSV_ABI sceMsgDialogClose();
int PS4_SYSV_ABI sceMsgDialogGetResult();
int PS4_SYSV_ABI sceMsgDialogGetStatus();
int PS4_SYSV_ABI sceMsgDialogInitialize();
s32 PS4_SYSV_ABI sceMsgDialogOpen(const OrbisMsgDialogParam* param);
int PS4_SYSV_ABI sceMsgDialogProgressBarInc();
int PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg();
int PS4_SYSV_ABI sceMsgDialogProgressBarSetValue();
int PS4_SYSV_ABI sceMsgDialogTerminate();
int PS4_SYSV_ABI sceMsgDialogUpdateStatus();
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogClose();
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result);
CommonDialog::Status PS4_SYSV_ABI sceMsgDialogGetStatus();
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogInitialize();
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogOpen(const OrbisParam* param);
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarInc(OrbisMsgDialogProgressBarTarget,
u32 delta);
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetMsg(OrbisMsgDialogProgressBarTarget,
const char* msg);
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogProgressBarSetValue(OrbisMsgDialogProgressBarTarget,
u32 value);
CommonDialog::Error PS4_SYSV_ABI sceMsgDialogTerminate();
CommonDialog::Status PS4_SYSV_ABI sceMsgDialogUpdateStatus();
void RegisterlibSceMsgDialog(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::MsgDialog

View file

@ -0,0 +1,273 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <imgui.h>
#include "common/assert.h"
#include "imgui/imgui_std.h"
#include "msgdialog_ui.h"
using namespace ImGui;
using namespace Libraries::CommonDialog;
using namespace Libraries::MsgDialog;
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
static constexpr float PROGRESS_BAR_WIDTH{0.8f};
struct {
int count = 0;
const char* text1;
const char* text2;
} static constexpr user_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(user_button_texts) == static_cast<int>(ButtonType::TWO_BUTTONS) + 1);
static void DrawCenteredText(const char* text) {
const auto ws = GetWindowSize();
const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f);
PushTextWrapPos(ws.x - 30.0f);
SetCursorPos({
(ws.x - text_size.x) / 2.0f,
(ws.y - text_size.y) / 2.0f - 50.0f,
});
Text("%s", text);
PopTextWrapPos();
}
MsgDialogState::MsgDialogState(const OrbisParam& param) {
this->mode = param.mode;
switch (mode) {
case MsgDialogMode::USER_MSG: {
ASSERT(param.userMsgParam);
const auto& v = *param.userMsgParam;
auto state = UserState{
.type = v.buttonType,
.msg = std::string(v.msg),
};
if (v.buttonType == ButtonType::TWO_BUTTONS) {
ASSERT(v.buttonsParam);
state.btn_param1 = std::string(v.buttonsParam->msg1);
state.btn_param2 = std::string(v.buttonsParam->msg2);
}
this->state = state;
} break;
case MsgDialogMode::PROGRESS_BAR: {
ASSERT(param.progBarParam);
const auto& v = *param.progBarParam;
this->state = ProgressState{
.type = v.barType,
.msg = std::string(v.msg),
.progress = 0,
};
} break;
case MsgDialogMode::SYSTEM_MSG: {
ASSERT(param.sysMsgParam);
const auto& v = *param.sysMsgParam;
this->state = SystemState{
.type = v.sysMsgType,
};
} break;
default:
UNREACHABLE_MSG("Unknown dialog mode");
}
}
void MsgDialogUi::DrawUser() {
const auto& [button_type, msg, btn_param1, btn_param2] =
state->GetState<MsgDialogState::UserState>();
const auto ws = GetWindowSize();
DrawCenteredText(msg.c_str());
ASSERT(button_type <= ButtonType::TWO_BUTTONS);
auto [count, text1, text2] = user_button_texts[static_cast<u32>(button_type)];
if (count == 0xFF) { // TWO_BUTTONS -> User defined message
count = 2;
text1 = btn_param1.c_str();
text2 = btn_param2.c_str();
}
const bool focus_first = button_type < ButtonType::YESNO_FOCUS_NO;
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast<float>(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)) {
switch (button_type) {
case ButtonType::OK_CANCEL:
case ButtonType::WAIT_CANCEL:
case ButtonType::OK_CANCEL_FOCUS_CANCEL:
Finish(ButtonId::INVALID, Result::USER_CANCELED);
break;
default:
Finish(ButtonId::BUTTON2);
break;
}
}
if (first_render && !focus_first) {
SetItemCurrentNavFocus();
}
PopID();
SameLine();
}
PushID(1);
if (Button(text1, BUTTON_SIZE)) {
Finish(ButtonId::BUTTON1);
}
if (first_render && focus_first) {
SetItemCurrentNavFocus();
}
PopID();
SameLine();
}
EndGroup();
}
void MsgDialogUi::DrawProgressBar() {
const auto& [bar_type, msg, progress_bar_value] =
state->GetState<MsgDialogState::ProgressState>();
DrawCenteredText(msg.c_str());
const auto ws = GetWindowSize();
SetCursorPos({
ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f),
ws.y - 10.0f - BUTTON_SIZE.y,
});
const bool has_cancel = bar_type == ProgressBarType::PERCENTAGE_CANCEL;
float bar_width = PROGRESS_BAR_WIDTH * ws.x;
if (has_cancel) {
bar_width -= BUTTON_SIZE.x - 10.0f;
}
BeginGroup();
ProgressBar(static_cast<float>(progress_bar_value) / 100.0f, {bar_width, BUTTON_SIZE.y});
if (has_cancel) {
SameLine();
if (Button("Cancel", BUTTON_SIZE)) {
Finish(ButtonId::INVALID, Result::USER_CANCELED);
}
if (first_render) {
SetItemCurrentNavFocus();
}
}
EndGroup();
}
struct {
const char* text;
} static constexpr system_message_texts[] = {
"No product available in the store.", // TRC_EMPTY_STORE
"PSN chat restriction.", // TRC_PSN_CHAT_RESTRICTION
"User-generated Media restriction", // TRC_PSN_UGC_RESTRICTION
nullptr, // !!NOP
"Camera not connected.", // CAMERA_NOT_CONNECTED
"Warning: profile picture and name are not set", // WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED
};
static_assert(std::size(system_message_texts) ==
static_cast<int>(SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED) + 1);
void MsgDialogUi::DrawSystemMessage() {
// TODO: Implement go to settings & user profile
const auto& [msg_type] = state->GetState<MsgDialogState::SystemState>();
ASSERT(msg_type <= SystemMessageType::WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED);
auto [msg] = system_message_texts[static_cast<u32>(msg_type)];
DrawCenteredText(msg);
const auto ws = GetWindowSize();
SetCursorPos({
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
ws.y - 10.0f - BUTTON_SIZE.y,
});
if (Button("OK", BUTTON_SIZE)) {
Finish(ButtonId::OK);
}
if (first_render) {
SetItemCurrentNavFocus();
}
}
MsgDialogUi::MsgDialogUi(MsgDialogState* state, Status* status, DialogResult* result)
: state(state), status(status), result(result) {
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
}
MsgDialogUi::~MsgDialogUi() {
Finish(ButtonId::INVALID);
}
MsgDialogUi::MsgDialogUi(MsgDialogUi&& other) noexcept
: Layer(other), state(other.state), status(other.status), result(other.result) {
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
}
MsgDialogUi& MsgDialogUi::operator=(MsgDialogUi other) {
using std::swap;
swap(state, other.state);
swap(status, other.status);
swap(result, other.result);
if (status && *status == Status::RUNNING) {
first_render = true;
AddLayer(this);
}
return *this;
}
void MsgDialogUi::Finish(ButtonId buttonId, Result r) {
if (result) {
result->result = r;
result->buttonId = buttonId;
}
if (status) {
*status = Status::FINISHED;
}
state = nullptr;
status = nullptr;
result = nullptr;
RemoveLayer(this);
}
void MsgDialogUi::Draw() {
if (status == nullptr || *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),
};
CentralizeWindow();
SetNextWindowSize(window_size);
SetNextWindowFocus();
SetNextWindowCollapsed(false);
KeepNavHighlight();
// Hack to allow every dialog to have a unique window
if (Begin("Message Dialog##MessageDialog", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
switch (state->GetMode()) {
case MsgDialogMode::USER_MSG:
DrawUser();
break;
case MsgDialogMode::PROGRESS_BAR:
DrawProgressBar();
break;
case MsgDialogMode::SYSTEM_MSG:
DrawSystemMessage();
break;
}
End();
}
first_render = false;
}

View file

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <variant>
#include "common/fixed_value.h"
#include "common/types.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
namespace Libraries::MsgDialog {
using OrbisUserServiceUserId = s32;
enum class MsgDialogMode : u32 {
USER_MSG = 1,
PROGRESS_BAR = 2,
SYSTEM_MSG = 3,
};
enum class ButtonId : u32 {
INVALID = 0,
OK = 1,
YES = 1,
NO = 2,
BUTTON1 = 1,
BUTTON2 = 2,
};
enum class ButtonType : u32 {
OK = 0,
YESNO = 1,
NONE = 2,
OK_CANCEL = 3,
WAIT = 5,
WAIT_CANCEL = 6,
YESNO_FOCUS_NO = 7,
OK_CANCEL_FOCUS_CANCEL = 8,
TWO_BUTTONS = 9,
};
enum class ProgressBarType : u32 {
PERCENTAGE = 0,
PERCENTAGE_CANCEL = 1,
};
enum class SystemMessageType : u32 {
TRC_EMPTY_STORE = 0,
TRC_PSN_CHAT_RESTRICTION = 1,
TRC_PSN_UGC_RESTRICTION = 2,
CAMERA_NOT_CONNECTED = 4,
WARNING_PROFILE_PICTURE_AND_NAME_NOT_SHARED = 5,
};
enum class OrbisMsgDialogProgressBarTarget : u32 {
DEFAULT = 0,
};
struct ButtonsParam {
const char* msg1{};
const char* msg2{};
std::array<char, 32> reserved{};
};
struct UserMessageParam {
ButtonType buttonType{};
s32 : 32;
const char* msg{};
ButtonsParam* buttonsParam{};
std::array<char, 24> reserved{};
};
struct ProgressBarParam {
ProgressBarType barType{};
s32 : 32;
const char* msg{};
std::array<char, 64> reserved{};
};
struct SystemMessageParam {
SystemMessageType sysMsgType{};
std::array<char, 32> reserved{};
};
struct OrbisParam {
CommonDialog::BaseParam baseParam;
std::size_t size;
MsgDialogMode mode;
s32 : 32;
UserMessageParam* userMsgParam;
ProgressBarParam* progBarParam;
SystemMessageParam* sysMsgParam;
OrbisUserServiceUserId userId;
std::array<char, 40> reserved;
s32 : 32;
};
struct DialogResult {
FixedValue<u32, 0> mode{};
CommonDialog::Result result{CommonDialog::Result::OK};
ButtonId buttonId{ButtonId::INVALID};
std::array<char, 32> reserved{};
};
// State is used to copy all the data from the param struct
class MsgDialogState {
public:
struct UserState {
ButtonType type{};
std::string msg{};
std::string btn_param1{};
std::string btn_param2{};
};
struct ProgressState {
ProgressBarType type{};
std::string msg{};
u32 progress{};
};
struct SystemState {
SystemMessageType type{};
};
private:
OrbisUserServiceUserId user_id{};
MsgDialogMode mode{};
std::variant<UserState, ProgressState, SystemState, std::monostate> state{std::monostate{}};
public:
explicit MsgDialogState(const OrbisParam& param);
MsgDialogState() = default;
[[nodiscard]] OrbisUserServiceUserId GetUserId() const {
return user_id;
}
[[nodiscard]] MsgDialogMode GetMode() const {
return mode;
}
template <typename T>
[[nodiscard]] T& GetState() {
return std::get<T>(state);
}
};
class MsgDialogUi final : public ImGui::Layer {
bool first_render{false};
MsgDialogState* state{};
CommonDialog::Status* status{};
DialogResult* result{};
void DrawUser();
void DrawProgressBar();
void DrawSystemMessage();
public:
explicit MsgDialogUi(MsgDialogState* state = nullptr, CommonDialog::Status* status = nullptr,
DialogResult* result = nullptr);
~MsgDialogUi() override;
MsgDialogUi(const MsgDialogUi& other) = delete;
MsgDialogUi(MsgDialogUi&& other) noexcept;
MsgDialogUi& operator=(MsgDialogUi other);
void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK);
void SetProgressBarValue(u32 value, bool increment);
void Draw() override;
bool ShouldGrabGamepad() override {
return true;
}
};
}; // namespace Libraries::MsgDialog

27
src/imgui/imgui_std.h Normal file
View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <imgui.h>
#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 SetItemCurrentNavFocus() {
const auto ctx = GetCurrentContext();
SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow);
ctx->NavInitResult.Clear();
}
} // namespace ImGui