mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-12-28 18:46:06 +00:00
ime_dialog: Initial implementation (#1267)
Some checks are pending
Build and Release / get-info (push) Waiting to run
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
Some checks are pending
Build and Release / get-info (push) Waiting to run
Build and Release / reuse (push) Waiting to run
Build and Release / clang-format (push) Waiting to run
Build and Release / windows-sdl (push) Blocked by required conditions
Build and Release / windows-qt (push) Blocked by required conditions
Build and Release / macos-sdl (push) Blocked by required conditions
Build and Release / macos-qt (push) Blocked by required conditions
Build and Release / linux-sdl (push) Blocked by required conditions
Build and Release / linux-qt (push) Blocked by required conditions
Build and Release / pre-release (push) Blocked by required conditions
* Add C string types * Prepare existing enums * Added missing enums * Types update * State base * Compile Ime Dialog UI * UI implementation * Scoped lock * Functional implementation * Link against iconv on macOS * Fix building on windows * Better UI * clang-format * Some fixes and cleanup * Enable reserved checks * clang-format * Fix default text encoding max size * clang-format (again) * Some review changes * Use std::vector for dynamic strings * Use CString * Accept dialog on enter press * clang-format * Use ImGUI for encoding/decoding
This commit is contained in:
parent
0f91661660
commit
66f1bb937f
2
CMakeLists.txt
Normal file → Executable file
2
CMakeLists.txt
Normal file → Executable file
|
@ -297,6 +297,8 @@ set(LIBC_SOURCES src/core/libraries/libc_internal/libc_internal.cpp
|
||||||
|
|
||||||
set(DIALOGS_LIB src/core/libraries/dialogs/error_dialog.cpp
|
set(DIALOGS_LIB src/core/libraries/dialogs/error_dialog.cpp
|
||||||
src/core/libraries/dialogs/error_dialog.h
|
src/core/libraries/dialogs/error_dialog.h
|
||||||
|
src/core/libraries/dialogs/ime_dialog_ui.cpp
|
||||||
|
src/core/libraries/dialogs/ime_dialog_ui.h
|
||||||
src/core/libraries/dialogs/ime_dialog.cpp
|
src/core/libraries/dialogs/ime_dialog.cpp
|
||||||
src/core/libraries/dialogs/ime_dialog.h
|
src/core/libraries/dialogs/ime_dialog.h
|
||||||
src/core/libraries/dialogs/error_codes.h
|
src/core/libraries/dialogs/error_codes.h
|
||||||
|
|
|
@ -81,34 +81,42 @@ public:
|
||||||
return std::basic_string_view<T>{data};
|
return std::basic_string_view<T>{data};
|
||||||
}
|
}
|
||||||
|
|
||||||
char* begin() {
|
T* begin() {
|
||||||
if (this == nullptr) {
|
if (this == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* begin() const {
|
const T* begin() const {
|
||||||
if (this == nullptr) {
|
if (this == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* end() {
|
T* end() {
|
||||||
if (this == nullptr) {
|
if (this == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return data + N;
|
return data + N;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* end() const {
|
const T* end() const {
|
||||||
if (this == nullptr) {
|
if (this == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return data + N;
|
return data + N;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t capacity() const {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const {
|
||||||
|
return std::char_traits<T>::length(data);
|
||||||
|
}
|
||||||
|
|
||||||
T& operator[](size_t idx) {
|
T& operator[](size_t idx) {
|
||||||
return data[idx];
|
return data[idx];
|
||||||
}
|
}
|
||||||
|
@ -152,6 +160,12 @@ public:
|
||||||
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
|
static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array
|
||||||
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
|
static_assert(std::weakly_incrementable<CString<13>::Iterator>);
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
using CWString = CString<N, wchar_t>;
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
using CU16String = CString<N, char16_t>;
|
||||||
|
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
|
@ -1,28 +1,75 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <magic_enum.hpp>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "ime_dialog.h"
|
#include "ime_dialog.h"
|
||||||
|
#include "ime_dialog_ui.h"
|
||||||
|
|
||||||
|
static constexpr std::array<float, 2> MAX_X_POSITIONS = {3840.0f, 1920.0f};
|
||||||
|
static constexpr std::array<float, 2> MAX_Y_POSITIONS = {2160.0f, 1080.0f};
|
||||||
|
|
||||||
namespace Libraries::ImeDialog {
|
namespace Libraries::ImeDialog {
|
||||||
|
|
||||||
static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE;
|
static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::NONE;
|
||||||
|
static OrbisImeDialogResult g_ime_dlg_result{};
|
||||||
|
static ImeDialogState g_ime_dlg_state{};
|
||||||
|
static ImeDialogUi g_ime_dlg_ui;
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogAbort() {
|
static bool IsValidOption(OrbisImeDialogOption option, OrbisImeType type) {
|
||||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
if (False(~option &
|
||||||
return ORBIS_OK;
|
(OrbisImeDialogOption::MULTILINE | OrbisImeDialogOption::NO_AUTO_COMPLETION))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (True(option & OrbisImeDialogOption::MULTILINE) && type != OrbisImeType::DEFAULT &&
|
||||||
|
type != OrbisImeType::BASIC_LATIN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (True(option & OrbisImeDialogOption::NO_AUTO_COMPLETION) && type != OrbisImeType::NUMBER &&
|
||||||
|
type != OrbisImeType::BASIC_LATIN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogForceClose() {
|
Error PS4_SYSV_ABI sceImeDialogAbort() {
|
||||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) {
|
||||||
return ORBIS_OK;
|
LOG_INFO(Lib_ImeDialog, "IME dialog not in use");
|
||||||
|
return Error::DIALOG_NOT_IN_USE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_ime_dlg_status != OrbisImeDialogStatus::RUNNING) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "IME dialog not running");
|
||||||
|
return Error::DIALOG_NOT_RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ime_dlg_status = OrbisImeDialogStatus::FINISHED;
|
||||||
|
g_ime_dlg_result.endstatus = OrbisImeDialogEndStatus::ABORTED;
|
||||||
|
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogForTestFunction() {
|
Error PS4_SYSV_ABI sceImeDialogForceClose() {
|
||||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) {
|
||||||
return ORBIS_OK;
|
LOG_INFO(Lib_ImeDialog, "IME dialog not in use");
|
||||||
|
return Error::DIALOG_NOT_IN_USE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ime_dlg_status = OrbisImeDialogStatus::NONE;
|
||||||
|
g_ime_dlg_ui = ImeDialogUi();
|
||||||
|
g_ime_dlg_state = ImeDialogState();
|
||||||
|
|
||||||
|
return Error::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error PS4_SYSV_ABI sceImeDialogForTestFunction() {
|
||||||
|
return Error::INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState() {
|
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState() {
|
||||||
|
@ -45,26 +92,118 @@ int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) {
|
Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) {
|
||||||
result->endstatus = OrbisImeDialogEndStatus::ORBIS_IME_DIALOG_END_STATUS_OK;
|
if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) {
|
||||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
LOG_INFO(Lib_ImeDialog, "IME dialog is not running");
|
||||||
return ORBIS_OK;
|
return Error::DIALOG_NOT_IN_USE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == nullptr) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "called with result (NULL)");
|
||||||
|
return Error::INVALID_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
result->endstatus = g_ime_dlg_result.endstatus;
|
||||||
|
|
||||||
|
if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) {
|
||||||
|
return Error::DIALOG_NOT_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ime_dlg_state.CopyTextToOrbisBuffer();
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogGetStatus() {
|
OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus() {
|
||||||
if (g_ime_dlg_status == OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING) {
|
if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) {
|
||||||
return OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_FINISHED;
|
g_ime_dlg_state.CallTextFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_ime_dlg_status;
|
return g_ime_dlg_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) {
|
Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) {
|
||||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
if (g_ime_dlg_status != OrbisImeDialogStatus::NONE) {
|
||||||
const std::wstring_view text = L"shadPS4";
|
LOG_INFO(Lib_ImeDialog, "IME dialog is already running");
|
||||||
param->maxTextLength = text.size();
|
return Error::BUSY;
|
||||||
std::memcpy(param->inputTextBuffer, text.data(), text.size() * sizeof(wchar_t));
|
}
|
||||||
g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING;
|
|
||||||
return ORBIS_OK;
|
if (param == nullptr) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "called with param (NULL)");
|
||||||
|
return Error::INVALID_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!magic_enum::enum_contains(param->type)) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->type");
|
||||||
|
return Error::INVALID_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do correct param->option validation
|
||||||
|
// TODO: do correct param->supportedLanguages validation
|
||||||
|
|
||||||
|
if (param->posx < 0.0f ||
|
||||||
|
param->posx >=
|
||||||
|
MAX_X_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->posx");
|
||||||
|
return Error::INVALID_POSX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param->posy < 0.0f ||
|
||||||
|
param->posy >=
|
||||||
|
MAX_Y_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->posy");
|
||||||
|
return Error::INVALID_POSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!magic_enum::enum_contains(param->horizontalAlignment)) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->horizontalAlignment");
|
||||||
|
return Error::INVALID_HORIZONTALIGNMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!magic_enum::enum_contains(param->verticalAlignment)) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->verticalAlignment");
|
||||||
|
return Error::INVALID_VERTICALALIGNMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsValidOption(param->option, param->type)) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->option");
|
||||||
|
return Error::INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param->inputTextBuffer == nullptr) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->inputTextBuffer");
|
||||||
|
return Error::INVALID_INPUT_TEXT_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extended) {
|
||||||
|
if (magic_enum::enum_contains(extended->priority)) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid extended->priority");
|
||||||
|
return Error::INVALID_EXTENDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do correct extended->option validation
|
||||||
|
|
||||||
|
if ((extended->extKeyboardMode & 0xe3fffffc) != 0) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid extended->extKeyboardMode");
|
||||||
|
return Error::INVALID_EXTENDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extended->disableDevice > 7) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid extended->disableDevice");
|
||||||
|
return Error::INVALID_EXTENDED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param->maxTextLength > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "Invalid param->maxTextLength");
|
||||||
|
return Error::INVALID_MAX_TEXT_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ime_dlg_result = {};
|
||||||
|
g_ime_dlg_state = ImeDialogState(param, extended);
|
||||||
|
g_ime_dlg_status = OrbisImeDialogStatus::RUNNING;
|
||||||
|
g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result);
|
||||||
|
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogInitInternal() {
|
int PS4_SYSV_ABI sceImeDialogInitInternal() {
|
||||||
|
@ -87,10 +226,22 @@ int PS4_SYSV_ABI sceImeDialogSetPanelPosition() {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogTerm() {
|
Error PS4_SYSV_ABI sceImeDialogTerm() {
|
||||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) {
|
||||||
g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE;
|
LOG_INFO(Lib_ImeDialog, "IME dialog not in use");
|
||||||
return ORBIS_OK;
|
return Error::DIALOG_NOT_IN_USE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) {
|
||||||
|
LOG_INFO(Lib_ImeDialog, "IME dialog is still running");
|
||||||
|
return Error::DIALOG_NOT_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_ime_dlg_status = OrbisImeDialogStatus::NONE;
|
||||||
|
g_ime_dlg_ui = ImeDialogUi();
|
||||||
|
g_ime_dlg_state = ImeDialogState();
|
||||||
|
|
||||||
|
return Error::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym) {
|
void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/enum.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Core::Loader {
|
namespace Core::Loader {
|
||||||
|
@ -11,71 +12,150 @@ class SymbolsResolver;
|
||||||
|
|
||||||
namespace Libraries::ImeDialog {
|
namespace Libraries::ImeDialog {
|
||||||
|
|
||||||
enum OrbisImeDialogStatus {
|
constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 0x78;
|
||||||
ORBIS_IME_DIALOG_STATUS_NONE = 0,
|
|
||||||
ORBIS_IME_DIALOG_STATUS_RUNNING = 1,
|
enum class Error : u32 {
|
||||||
ORBIS_IME_DIALOG_STATUS_FINISHED = 2
|
OK = 0x0,
|
||||||
|
BUSY = 0x80bc0001,
|
||||||
|
NOT_OPENED = 0x80bc0002,
|
||||||
|
NO_MEMORY = 0x80bc0003,
|
||||||
|
CONNECTION_FAILED = 0x80bc0004,
|
||||||
|
TOO_MANY_REQUESTS = 0x80bc0005,
|
||||||
|
INVALID_TEXT = 0x80bc0006,
|
||||||
|
EVENT_OVERFLOW = 0x80bc0007,
|
||||||
|
NOT_ACTIVE = 0x80bc0008,
|
||||||
|
IME_SUSPENDING = 0x80bc0009,
|
||||||
|
DEVICE_IN_USE = 0x80bc000a,
|
||||||
|
INVALID_USER_ID = 0x80bc0010,
|
||||||
|
INVALID_TYPE = 0x80bc0011,
|
||||||
|
INVALID_SUPPORTED_LANGUAGES = 0x80bc0012,
|
||||||
|
INVALID_ENTER_LABEL = 0x80bc0013,
|
||||||
|
INVALID_INPUT_METHOD = 0x80bc0014,
|
||||||
|
INVALID_OPTION = 0x80bc0015,
|
||||||
|
INVALID_MAX_TEXT_LENGTH = 0x80bc0016,
|
||||||
|
INVALID_INPUT_TEXT_BUFFER = 0x80bc0017,
|
||||||
|
INVALID_POSX = 0x80bc0018,
|
||||||
|
INVALID_POSY = 0x80bc0019,
|
||||||
|
INVALID_HORIZONTALIGNMENT = 0x80bc001a,
|
||||||
|
INVALID_VERTICALALIGNMENT = 0x80bc001b,
|
||||||
|
INVALID_EXTENDED = 0x80bc001c,
|
||||||
|
INVALID_KEYBOARD_TYPE = 0x80bc001d,
|
||||||
|
INVALID_WORK = 0x80bc0020,
|
||||||
|
INVALID_ARG = 0x80bc0021,
|
||||||
|
INVALID_HANDLER = 0x80bc0022,
|
||||||
|
NO_RESOURCE_ID = 0x80bc0023,
|
||||||
|
INVALID_MODE = 0x80bc0024,
|
||||||
|
INVALID_PARAM = 0x80bc0030,
|
||||||
|
INVALID_ADDRESS = 0x80bc0031,
|
||||||
|
INVALID_RESERVED = 0x80bc0032,
|
||||||
|
INVALID_TIMING = 0x80bc0033,
|
||||||
|
INTERNAL = 0x80bc00ff,
|
||||||
|
DIALOG_INVALID_TITLE = 0x80bc0101,
|
||||||
|
DIALOG_NOT_RUNNING = 0x80bc0105,
|
||||||
|
DIALOG_NOT_FINISHED = 0x80bc0106,
|
||||||
|
DIALOG_NOT_IN_USE = 0x80bc0107,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OrbisImeDialogEndStatus {
|
enum class OrbisImeDialogStatus : u32 {
|
||||||
ORBIS_IME_DIALOG_END_STATUS_OK = 0,
|
NONE = 0,
|
||||||
ORBIS_IME_DIALOG_END_STATUS_USER_CANCELED = 1,
|
RUNNING = 1,
|
||||||
ORBIS_IME_DIALOG_END_STATUS_ABORTED = 2
|
FINISHED = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OrbisImeDialogResult {
|
enum class OrbisImeDialogEndStatus : u32 {
|
||||||
OrbisImeDialogEndStatus endstatus;
|
OK = 0,
|
||||||
s32 reserved[12];
|
USER_CANCELED = 1,
|
||||||
|
ABORTED = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OrbisImeType {
|
enum class OrbisImeType : u32 {
|
||||||
ORBIS_IME_TYPE_DEFAULT = 0,
|
DEFAULT = 0,
|
||||||
ORBIS_IME_TYPE_BASIC_LATIN = 1,
|
BASIC_LATIN = 1,
|
||||||
ORBIS_IME_TYPE_URL = 2,
|
URL = 2,
|
||||||
ORBIS_IME_TYPE_MAIL = 3,
|
MAIL = 3,
|
||||||
ORBIS_IME_TYPE_NUMBER = 4
|
NUMBER = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OrbisImeEnterLabel {
|
enum class OrbisImeEnterLabel : u32 {
|
||||||
ORBIS_IME_ENTER_LABEL_DEFAULT = 0,
|
DEFAULT = 0,
|
||||||
ORBIS_IME_ENTER_LABEL_SEND = 1,
|
SEND = 1,
|
||||||
ORBIS_IME_ENTER_LABEL_SEARCH = 2,
|
SEARCH = 2,
|
||||||
ORBIS_IME_ENTER_LABEL_GO = 3
|
GO = 3,
|
||||||
};
|
|
||||||
enum OrbiImeInputMethod { ORBIS_IME_INPUT_METHOD_DEFAULT = 0 };
|
|
||||||
|
|
||||||
typedef int (*OrbisImeTextFilter)(wchar_t* outText, u32* outTextLength, const wchar_t* srcText,
|
|
||||||
u32 srcTextLength);
|
|
||||||
|
|
||||||
enum OrbisImeHorizontalAlignment {
|
|
||||||
ORBIS_IME_HALIGN_LEFT = 0,
|
|
||||||
ORBIS_IME_HALIGN_CENTER = 1,
|
|
||||||
ORBIS_IME_HALIGN_RIGHT = 2
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OrbisImeVerticalAlignment {
|
enum class OrbisImeDialogOption : u32 {
|
||||||
ORBIS_IME_VALIGN_TOP = 0,
|
DEFAULT = 0,
|
||||||
ORBIS_IME_VALIGN_CENTER = 1,
|
MULTILINE = 1,
|
||||||
ORBIS_IME_VALIGN_BOTTOM = 2
|
NO_AUTO_CORRECTION = 2,
|
||||||
|
NO_AUTO_COMPLETION = 4,
|
||||||
|
// TODO: Document missing options
|
||||||
|
LARGE_RESOLUTION = 1024,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OrbisImeDialogParam {
|
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption)
|
||||||
s32 userId;
|
|
||||||
OrbisImeType type;
|
enum class OrbisImeInputMethod : u32 {
|
||||||
u64 supportedLanguages;
|
DEFAULT = 0,
|
||||||
OrbisImeEnterLabel enterLabel;
|
};
|
||||||
OrbiImeInputMethod inputMethod;
|
|
||||||
OrbisImeTextFilter filter;
|
enum class OrbisImeHorizontalAlignment : u32 {
|
||||||
u32 option;
|
LEFT = 0,
|
||||||
u32 maxTextLength;
|
CENTER = 1,
|
||||||
wchar_t* inputTextBuffer;
|
RIGHT = 2,
|
||||||
float posx;
|
};
|
||||||
float posy;
|
|
||||||
OrbisImeHorizontalAlignment horizontalAlignment;
|
enum class OrbisImeVerticalAlignment : u32 {
|
||||||
OrbisImeVerticalAlignment verticalAlignment;
|
TOP = 0,
|
||||||
const wchar_t* placeholder;
|
CENTER = 1,
|
||||||
const wchar_t* title;
|
BOTTOM = 2,
|
||||||
s8 reserved[16];
|
};
|
||||||
|
|
||||||
|
enum class OrbisImePanelPriority : u32 {
|
||||||
|
DEFAULT = 0,
|
||||||
|
ALPHABET = 1,
|
||||||
|
SYMBOL = 2,
|
||||||
|
ACCENT = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class OrbisImeKeyboardType : u32 {
|
||||||
|
NONE = 0,
|
||||||
|
DANISH = 1,
|
||||||
|
GERMAN = 2,
|
||||||
|
GERMAN_SW = 3,
|
||||||
|
ENGLISH_US = 4,
|
||||||
|
ENGLISH_GB = 5,
|
||||||
|
SPANISH = 6,
|
||||||
|
SPANISH_LA = 7,
|
||||||
|
FINNISH = 8,
|
||||||
|
FRENCH = 9,
|
||||||
|
FRENCH_BR = 10,
|
||||||
|
FRENCH_CA = 11,
|
||||||
|
FRENCH_SW = 12,
|
||||||
|
ITALIAN = 13,
|
||||||
|
DUTCH = 14,
|
||||||
|
NORWEGIAN = 15,
|
||||||
|
POLISH = 16,
|
||||||
|
PORTUGUESE_BR = 17,
|
||||||
|
PORTUGUESE_PT = 18,
|
||||||
|
RUSSIAN = 19,
|
||||||
|
SWEDISH = 20,
|
||||||
|
TURKISH = 21,
|
||||||
|
JAPANESE_ROMAN = 22,
|
||||||
|
JAPANESE_KANA = 23,
|
||||||
|
KOREAN = 24,
|
||||||
|
SM_CHINESE = 25,
|
||||||
|
TR_CHINESE_ZY = 26,
|
||||||
|
TR_CHINESE_PY_HK = 27,
|
||||||
|
TR_CHINESE_PY_TW = 28,
|
||||||
|
TR_CHINESE_CG = 29,
|
||||||
|
ARABIC_AR = 30,
|
||||||
|
THAI = 31,
|
||||||
|
CZECH = 32,
|
||||||
|
GREEK = 33,
|
||||||
|
INDONESIAN = 34,
|
||||||
|
VIETNAMESE = 35,
|
||||||
|
ROMANIAN = 36,
|
||||||
|
HUNGARIAN = 37,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OrbisImeColor {
|
struct OrbisImeColor {
|
||||||
|
@ -85,57 +165,14 @@ struct OrbisImeColor {
|
||||||
u8 a;
|
u8 a;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OrbisImePanelPriority {
|
struct OrbisImeDialogResult {
|
||||||
ORBIS_IME_PANEL_PRIORITY_DEFAULT = 0,
|
OrbisImeDialogEndStatus endstatus;
|
||||||
ORBIS_IME_PANEL_PRIORITY_ALPHABET = 1,
|
s32 reserved[12];
|
||||||
ORBIS_IME_PANEL_PRIORITY_SYMBOL = 2,
|
|
||||||
ORBIS_IME_PANEL_PRIORITY_ACCENT = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OrbisImeKeyboardType {
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_NONE = 0,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_DANISH = 1,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_GERMAN = 2,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_GERMAN_SW = 3,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_ENGLISH_US = 4,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_ENGLISH_GB = 5,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_SPANISH = 6,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_SPANISH_LA = 7,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_FINNISH = 8,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_FRENCH = 9,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_FRENCH_BR = 10,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_FRENCH_CA = 11,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_FRENCH_SW = 12,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_ITALIAN = 13,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_DUTCH = 14,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_NORWEGIAN = 15,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_POLISH = 16,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_BR = 17,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_PT = 18,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_RUSSIAN = 19,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_SWEDISH = 20,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_TURKISH = 21,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_JAPANESE_ROMAN = 22,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_JAPANESE_KANA = 23,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_KOREAN = 24,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_SM_CHINESE = 25,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_ZY = 26,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_HK = 27,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_TW = 28,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_CG = 29,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_ARABIC_AR = 30,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_THAI = 31,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_CZECH = 32,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_GREEK = 33,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_INDONESIAN = 34,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_VIETNAMESE = 35,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_ROMANIAN = 36,
|
|
||||||
ORBIS_IME_KEYBOARD_TYPE_HUNGARIAN = 37
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OrbisImeKeycode {
|
struct OrbisImeKeycode {
|
||||||
u16 keycode;
|
u16 keycode;
|
||||||
wchar_t character;
|
char16_t character;
|
||||||
u32 status;
|
u32 status;
|
||||||
OrbisImeKeyboardType type;
|
OrbisImeKeyboardType type;
|
||||||
s32 userId;
|
s32 userId;
|
||||||
|
@ -143,11 +180,34 @@ struct OrbisImeKeycode {
|
||||||
u64 timestamp;
|
u64 timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, u16* outKeycode,
|
typedef PS4_SYSV_ABI int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength,
|
||||||
u32* outStatus, void* reserved);
|
const char16_t* srcText, u32 srcTextLength);
|
||||||
|
|
||||||
|
typedef PS4_SYSV_ABI int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode,
|
||||||
|
u16* outKeycode, u32* outStatus,
|
||||||
|
void* reserved);
|
||||||
|
|
||||||
|
struct OrbisImeDialogParam {
|
||||||
|
s32 userId;
|
||||||
|
OrbisImeType type;
|
||||||
|
u64 supportedLanguages;
|
||||||
|
OrbisImeEnterLabel enterLabel;
|
||||||
|
OrbisImeInputMethod inputMethod;
|
||||||
|
OrbisImeTextFilter filter;
|
||||||
|
OrbisImeDialogOption option;
|
||||||
|
u32 maxTextLength;
|
||||||
|
char16_t* inputTextBuffer;
|
||||||
|
float posx;
|
||||||
|
float posy;
|
||||||
|
OrbisImeHorizontalAlignment horizontalAlignment;
|
||||||
|
OrbisImeVerticalAlignment verticalAlignment;
|
||||||
|
const char16_t* placeholder;
|
||||||
|
const char16_t* title;
|
||||||
|
s8 reserved[16];
|
||||||
|
};
|
||||||
|
|
||||||
struct OrbisImeParamExtended {
|
struct OrbisImeParamExtended {
|
||||||
u32 option;
|
u32 option; // OrbisImeDialogOptionExtended
|
||||||
OrbisImeColor colorBase;
|
OrbisImeColor colorBase;
|
||||||
OrbisImeColor colorLine;
|
OrbisImeColor colorLine;
|
||||||
OrbisImeColor colorTextField;
|
OrbisImeColor colorTextField;
|
||||||
|
@ -165,21 +225,21 @@ struct OrbisImeParamExtended {
|
||||||
int8_t reserved[60];
|
int8_t reserved[60];
|
||||||
};
|
};
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceImeDialogAbort();
|
Error PS4_SYSV_ABI sceImeDialogAbort();
|
||||||
int PS4_SYSV_ABI sceImeDialogForceClose();
|
Error PS4_SYSV_ABI sceImeDialogForceClose();
|
||||||
int PS4_SYSV_ABI sceImeDialogForTestFunction();
|
Error PS4_SYSV_ABI sceImeDialogForTestFunction();
|
||||||
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState();
|
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState();
|
||||||
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm();
|
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm();
|
||||||
int PS4_SYSV_ABI sceImeDialogGetPanelSize();
|
int PS4_SYSV_ABI sceImeDialogGetPanelSize();
|
||||||
int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended();
|
int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended();
|
||||||
int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result);
|
Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result);
|
||||||
/*OrbisImeDialogStatus*/ int PS4_SYSV_ABI sceImeDialogGetStatus();
|
OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus();
|
||||||
int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended);
|
Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended);
|
||||||
int PS4_SYSV_ABI sceImeDialogInitInternal();
|
int PS4_SYSV_ABI sceImeDialogInitInternal();
|
||||||
int PS4_SYSV_ABI sceImeDialogInitInternal2();
|
int PS4_SYSV_ABI sceImeDialogInitInternal2();
|
||||||
int PS4_SYSV_ABI sceImeDialogInitInternal3();
|
int PS4_SYSV_ABI sceImeDialogInitInternal3();
|
||||||
int PS4_SYSV_ABI sceImeDialogSetPanelPosition();
|
int PS4_SYSV_ABI sceImeDialogSetPanelPosition();
|
||||||
int PS4_SYSV_ABI sceImeDialogTerm();
|
Error PS4_SYSV_ABI sceImeDialogTerm();
|
||||||
|
|
||||||
void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym);
|
void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym);
|
||||||
} // namespace Libraries::ImeDialog
|
} // namespace Libraries::ImeDialog
|
390
src/core/libraries/dialogs/ime_dialog_ui.cpp
Normal file
390
src/core/libraries/dialogs/ime_dialog_ui.cpp
Normal file
|
@ -0,0 +1,390 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <cwchar>
|
||||||
|
#include <string>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <magic_enum.hpp>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/singleton.h"
|
||||||
|
#include "core/libraries/dialogs/ime_dialog.h"
|
||||||
|
#include "core/libraries/dialogs/ime_dialog_ui.h"
|
||||||
|
#include "core/linker.h"
|
||||||
|
#include "imgui/imgui_std.h"
|
||||||
|
|
||||||
|
using namespace ImGui;
|
||||||
|
|
||||||
|
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
|
||||||
|
|
||||||
|
namespace Libraries::ImeDialog {
|
||||||
|
|
||||||
|
ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||||
|
const OrbisImeParamExtended* extended) {
|
||||||
|
if (!param)
|
||||||
|
return;
|
||||||
|
|
||||||
|
userId = param->userId;
|
||||||
|
is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE);
|
||||||
|
is_numeric = param->type == OrbisImeType::NUMBER;
|
||||||
|
type = param->type;
|
||||||
|
enter_label = param->enterLabel;
|
||||||
|
text_filter = param->filter;
|
||||||
|
keyboard_filter = extended ? extended->extKeyboardFilter : nullptr;
|
||||||
|
max_text_length = param->maxTextLength;
|
||||||
|
text_buffer = param->inputTextBuffer;
|
||||||
|
|
||||||
|
if (param->title) {
|
||||||
|
std::size_t title_len = std::char_traits<char16_t>::length(param->title);
|
||||||
|
title.resize(title_len * 4 + 1);
|
||||||
|
title[title_len * 4] = '\0';
|
||||||
|
|
||||||
|
if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) {
|
||||||
|
LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param->placeholder) {
|
||||||
|
std::size_t placeholder_len = std::char_traits<char16_t>::length(param->placeholder);
|
||||||
|
placeholder.resize(placeholder_len * 4 + 1);
|
||||||
|
placeholder[placeholder_len * 4] = '\0';
|
||||||
|
|
||||||
|
if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0],
|
||||||
|
placeholder_len * 4)) {
|
||||||
|
LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t text_len = std::char_traits<char16_t>::length(text_buffer);
|
||||||
|
if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
|
||||||
|
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) {
|
||||||
|
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImeDialogState::ImeDialogState(ImeDialogState&& other) noexcept
|
||||||
|
: input_changed(other.input_changed), userId(other.userId), is_multiLine(other.is_multiLine),
|
||||||
|
is_numeric(other.is_numeric), type(other.type), enter_label(other.enter_label),
|
||||||
|
text_filter(other.text_filter), keyboard_filter(other.keyboard_filter),
|
||||||
|
max_text_length(other.max_text_length), text_buffer(other.text_buffer),
|
||||||
|
title(std::move(other.title)), placeholder(std::move(other.placeholder)),
|
||||||
|
current_text(other.current_text) {
|
||||||
|
|
||||||
|
other.text_buffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
input_changed = other.input_changed;
|
||||||
|
userId = other.userId;
|
||||||
|
is_multiLine = other.is_multiLine;
|
||||||
|
is_numeric = other.is_numeric;
|
||||||
|
type = other.type;
|
||||||
|
enter_label = other.enter_label;
|
||||||
|
text_filter = other.text_filter;
|
||||||
|
keyboard_filter = other.keyboard_filter;
|
||||||
|
max_text_length = other.max_text_length;
|
||||||
|
text_buffer = other.text_buffer;
|
||||||
|
title = std::move(other.title);
|
||||||
|
placeholder = std::move(other.placeholder);
|
||||||
|
current_text = other.current_text;
|
||||||
|
|
||||||
|
other.text_buffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImeDialogState::CopyTextToOrbisBuffer() {
|
||||||
|
if (!text_buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer,
|
||||||
|
max_text_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImeDialogState::CallTextFilter() {
|
||||||
|
if (!text_filter || !input_changed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
input_changed = false;
|
||||||
|
|
||||||
|
char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0};
|
||||||
|
u32 src_text_length = current_text.size();
|
||||||
|
char16_t out_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0};
|
||||||
|
u32 out_text_length = ORBIS_IME_DIALOG_MAX_TEXT_LENGTH;
|
||||||
|
|
||||||
|
if (!ConvertUTF8ToOrbis(current_text.begin(), src_text_length, src_text,
|
||||||
|
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)) {
|
||||||
|
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to orbis encoding");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
|
int ret =
|
||||||
|
linker->ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length);
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ConvertOrbisToUTF8(out_text, out_text_length, current_text.begin(),
|
||||||
|
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) {
|
||||||
|
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode,
|
||||||
|
u32* out_status) {
|
||||||
|
if (!keyboard_filter) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||||
|
int ret = linker->ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr);
|
||||||
|
|
||||||
|
return ret == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len,
|
||||||
|
char* utf8_text, std::size_t utf8_text_len) {
|
||||||
|
|
||||||
|
std::fill(utf8_text, utf8_text + utf8_text_len, '\0');
|
||||||
|
const ImWchar* orbis_text_ptr = reinterpret_cast<const ImWchar*>(orbis_text);
|
||||||
|
ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len,
|
||||||
|
char16_t* orbis_text, std::size_t orbis_text_len) {
|
||||||
|
|
||||||
|
std::fill(orbis_text, orbis_text + orbis_text_len, u'\0');
|
||||||
|
ImTextStrFromUtf8(reinterpret_cast<ImWchar*>(orbis_text), orbis_text_len, utf8_text, nullptr);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImeDialogUi::ImeDialogUi(ImeDialogState* state, OrbisImeDialogStatus* status,
|
||||||
|
OrbisImeDialogResult* result)
|
||||||
|
: state(state), status(status), result(result) {
|
||||||
|
|
||||||
|
if (state && *status == OrbisImeDialogStatus::RUNNING) {
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImeDialogUi::~ImeDialogUi() {
|
||||||
|
std::scoped_lock lock(draw_mutex);
|
||||||
|
|
||||||
|
Free();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImeDialogUi::ImeDialogUi(ImeDialogUi&& other) noexcept
|
||||||
|
: state(other.state), status(other.status), result(other.result),
|
||||||
|
first_render(other.first_render) {
|
||||||
|
|
||||||
|
std::scoped_lock lock(draw_mutex, other.draw_mutex);
|
||||||
|
other.state = nullptr;
|
||||||
|
other.status = nullptr;
|
||||||
|
other.result = nullptr;
|
||||||
|
|
||||||
|
if (state && *status == OrbisImeDialogStatus::RUNNING) {
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImeDialogUi& ImeDialogUi::operator=(ImeDialogUi&& other) {
|
||||||
|
std::scoped_lock lock(draw_mutex, other.draw_mutex);
|
||||||
|
Free();
|
||||||
|
|
||||||
|
state = other.state;
|
||||||
|
status = other.status;
|
||||||
|
result = other.result;
|
||||||
|
first_render = other.first_render;
|
||||||
|
other.state = nullptr;
|
||||||
|
other.status = nullptr;
|
||||||
|
other.result = nullptr;
|
||||||
|
|
||||||
|
if (state && *status == OrbisImeDialogStatus::RUNNING) {
|
||||||
|
AddLayer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImeDialogUi::Free() {
|
||||||
|
RemoveLayer(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImeDialogUi::Draw() {
|
||||||
|
std::unique_lock lock{draw_mutex};
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status || *status != OrbisImeDialogStatus::RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& ctx = *GetCurrentContext();
|
||||||
|
const auto& io = ctx.IO;
|
||||||
|
|
||||||
|
ImVec2 window_size;
|
||||||
|
|
||||||
|
if (state->is_multiLine) {
|
||||||
|
window_size = {500.0f, 300.0f};
|
||||||
|
} else {
|
||||||
|
window_size = {500.0f, 150.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
CentralizeWindow();
|
||||||
|
SetNextWindowSize(window_size);
|
||||||
|
SetNextWindowCollapsed(false);
|
||||||
|
|
||||||
|
if (first_render || !io.NavActive) {
|
||||||
|
SetNextWindowFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Begin("IME Dialog##ImeDialog", nullptr,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||||
|
DrawPrettyBackground();
|
||||||
|
|
||||||
|
if (!state->title.empty()) {
|
||||||
|
SetWindowFontScale(1.7f);
|
||||||
|
TextUnformatted(state->title.data());
|
||||||
|
SetWindowFontScale(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->is_multiLine) {
|
||||||
|
DrawMultiLineInputText();
|
||||||
|
} else {
|
||||||
|
DrawInputText();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCursorPosY(GetCursorPosY() + 10.0f);
|
||||||
|
|
||||||
|
const char* button_text;
|
||||||
|
|
||||||
|
switch (state->enter_label) {
|
||||||
|
case OrbisImeEnterLabel::GO:
|
||||||
|
button_text = "Go##ImeDialogOK";
|
||||||
|
break;
|
||||||
|
case OrbisImeEnterLabel::SEARCH:
|
||||||
|
button_text = "Search##ImeDialogOK";
|
||||||
|
break;
|
||||||
|
case OrbisImeEnterLabel::SEND:
|
||||||
|
button_text = "Send##ImeDialogOK";
|
||||||
|
break;
|
||||||
|
case OrbisImeEnterLabel::DEFAULT:
|
||||||
|
default:
|
||||||
|
button_text = "OK##ImeDialogOK";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
float button_spacing = 10.0f;
|
||||||
|
float total_button_width = BUTTON_SIZE.x * 2 + button_spacing;
|
||||||
|
float button_start_pos = (window_size.x - total_button_width) / 2.0f;
|
||||||
|
|
||||||
|
SetCursorPosX(button_start_pos);
|
||||||
|
|
||||||
|
if (Button(button_text, BUTTON_SIZE) ||
|
||||||
|
(!state->is_multiLine && IsKeyPressed(ImGuiKey_Enter))) {
|
||||||
|
*status = OrbisImeDialogStatus::FINISHED;
|
||||||
|
result->endstatus = OrbisImeDialogEndStatus::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine(0.0f, button_spacing);
|
||||||
|
|
||||||
|
if (Button("Cancel##ImeDialogCancel", BUTTON_SIZE)) {
|
||||||
|
*status = OrbisImeDialogStatus::FINISHED;
|
||||||
|
result->endstatus = OrbisImeDialogEndStatus::USER_CANCELED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
|
||||||
|
first_render = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImeDialogUi::DrawInputText() {
|
||||||
|
ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f};
|
||||||
|
SetCursorPosX(20.0f);
|
||||||
|
if (first_render) {
|
||||||
|
SetKeyboardFocusHere();
|
||||||
|
}
|
||||||
|
const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data();
|
||||||
|
if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(),
|
||||||
|
state->max_text_length, input_size, ImGuiInputTextFlags_CallbackCharFilter,
|
||||||
|
InputTextCallback, this)) {
|
||||||
|
state->input_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImeDialogUi::DrawMultiLineInputText() {
|
||||||
|
ImVec2 input_size = {GetWindowWidth() - 40.0f, 200.0f};
|
||||||
|
SetCursorPosX(20.0f);
|
||||||
|
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackCharFilter |
|
||||||
|
static_cast<ImGuiInputTextFlags>(ImGuiInputTextFlags_Multiline);
|
||||||
|
if (first_render) {
|
||||||
|
SetKeyboardFocusHere();
|
||||||
|
}
|
||||||
|
const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data();
|
||||||
|
if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(),
|
||||||
|
state->max_text_length, input_size, flags, InputTextCallback, this)) {
|
||||||
|
state->input_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
||||||
|
ImeDialogUi* ui = static_cast<ImeDialogUi*>(data->UserData);
|
||||||
|
|
||||||
|
ASSERT(ui);
|
||||||
|
|
||||||
|
// Should we filter punctuation?
|
||||||
|
if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') &&
|
||||||
|
data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ui->state->keyboard_filter) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImGui encodes ImWchar32 as multi-byte UTF-8 characters
|
||||||
|
char* event_char = reinterpret_cast<char*>(&data->EventChar);
|
||||||
|
|
||||||
|
// Call the keyboard filter
|
||||||
|
OrbisImeKeycode src_keycode = {
|
||||||
|
.keycode = 0,
|
||||||
|
.character = 0,
|
||||||
|
.status = 1, // ??? 1 = key pressed, 0 = key released
|
||||||
|
.type = OrbisImeKeyboardType::ENGLISH_US, // TODO set this to the correct value (maybe use
|
||||||
|
// the current language?)
|
||||||
|
.userId = ui->state->userId,
|
||||||
|
.resourceId = 0,
|
||||||
|
.timestamp = 0};
|
||||||
|
|
||||||
|
if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) {
|
||||||
|
LOG_ERROR(Lib_ImeDialog, "Failed to convert orbis char to utf8");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
src_keycode.keycode = src_keycode.character; // TODO set this to the correct value
|
||||||
|
|
||||||
|
u16 out_keycode;
|
||||||
|
u32 out_status;
|
||||||
|
|
||||||
|
ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status);
|
||||||
|
|
||||||
|
// TODO. set the keycode
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libraries::ImeDialog
|
84
src/core/libraries/dialogs/ime_dialog_ui.h
Normal file
84
src/core/libraries/dialogs/ime_dialog_ui.h
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include "common/cstring.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "core/libraries/dialogs/ime_dialog.h"
|
||||||
|
#include "imgui/imgui_layer.h"
|
||||||
|
|
||||||
|
namespace Libraries::ImeDialog {
|
||||||
|
|
||||||
|
class ImeDialogUi;
|
||||||
|
|
||||||
|
class ImeDialogState final {
|
||||||
|
friend ImeDialogUi;
|
||||||
|
|
||||||
|
bool input_changed = false;
|
||||||
|
|
||||||
|
s32 userId{};
|
||||||
|
bool is_multiLine{};
|
||||||
|
bool is_numeric{};
|
||||||
|
OrbisImeType type{};
|
||||||
|
OrbisImeEnterLabel enter_label{};
|
||||||
|
OrbisImeTextFilter text_filter{};
|
||||||
|
OrbisImeExtKeyboardFilter keyboard_filter{};
|
||||||
|
u32 max_text_length{};
|
||||||
|
char16_t* text_buffer{};
|
||||||
|
std::vector<char> title;
|
||||||
|
std::vector<char> placeholder;
|
||||||
|
|
||||||
|
// A character can hold up to 4 bytes in UTF-8
|
||||||
|
Common::CString<ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4> current_text;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ImeDialogState(const OrbisImeDialogParam* param = nullptr,
|
||||||
|
const OrbisImeParamExtended* extended = nullptr);
|
||||||
|
ImeDialogState(const ImeDialogState& other) = delete;
|
||||||
|
ImeDialogState(ImeDialogState&& other) noexcept;
|
||||||
|
ImeDialogState& operator=(ImeDialogState&& other);
|
||||||
|
|
||||||
|
bool CopyTextToOrbisBuffer();
|
||||||
|
bool CallTextFilter();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status);
|
||||||
|
|
||||||
|
bool ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text,
|
||||||
|
std::size_t native_text_len);
|
||||||
|
bool ConvertUTF8ToOrbis(const char* native_text, std::size_t utf8_text_len,
|
||||||
|
char16_t* orbis_text, std::size_t orbis_text_len);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImeDialogUi final : public ImGui::Layer {
|
||||||
|
ImeDialogState* state{};
|
||||||
|
OrbisImeDialogStatus* status{};
|
||||||
|
OrbisImeDialogResult* result{};
|
||||||
|
|
||||||
|
bool first_render = true;
|
||||||
|
std::mutex draw_mutex;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ImeDialogUi(ImeDialogState* state = nullptr, OrbisImeDialogStatus* status = nullptr,
|
||||||
|
OrbisImeDialogResult* result = nullptr);
|
||||||
|
~ImeDialogUi() override;
|
||||||
|
ImeDialogUi(const ImeDialogUi& other) = delete;
|
||||||
|
ImeDialogUi(ImeDialogUi&& other) noexcept;
|
||||||
|
ImeDialogUi& operator=(ImeDialogUi&& other);
|
||||||
|
|
||||||
|
void Draw() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Free();
|
||||||
|
|
||||||
|
void DrawInputText();
|
||||||
|
void DrawMultiLineInputText();
|
||||||
|
|
||||||
|
static int InputTextCallback(ImGuiInputTextCallbackData* data);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libraries::ImeDialog
|
|
@ -21,7 +21,6 @@ extern void assert_fail_debug_msg(const char* msg);
|
||||||
} \
|
} \
|
||||||
}())
|
}())
|
||||||
|
|
||||||
#define IMGUI_USE_WCHAR32
|
|
||||||
#define IMGUI_ENABLE_STB_TRUETYPE
|
#define IMGUI_ENABLE_STB_TRUETYPE
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
|
||||||
|
@ -30,3 +29,7 @@ extern void assert_fail_debug_msg(const char* msg);
|
||||||
|
|
||||||
#define IM_VEC4_CLASS_EXTRA \
|
#define IM_VEC4_CLASS_EXTRA \
|
||||||
constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {}
|
constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {}
|
||||||
|
|
||||||
|
#ifdef IMGUI_USE_WCHAR32
|
||||||
|
#error "This project uses 16 bits wchar standard like Orbis"
|
||||||
|
#endif
|
Loading…
Reference in a new issue