mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-02 11:03:34 -06:00
Merge c1808bc8d0 into 969955b8a0
This commit is contained in:
commit
35cc949dcf
@ -518,6 +518,8 @@ set(HLE_LIBC_INTERNAL_LIB src/core/libraries/libc_internal/libc_internal.cpp
|
||||
set(IME_LIB src/core/libraries/ime/error_dialog.cpp
|
||||
src/core/libraries/ime/error_dialog.h
|
||||
src/core/libraries/ime/ime_common.h
|
||||
src/core/libraries/ime/ime_kb_layout.cpp
|
||||
src/core/libraries/ime/ime_kb_layout.h
|
||||
src/core/libraries/ime/ime_dialog_ui.cpp
|
||||
src/core/libraries/ime/ime_dialog_ui.h
|
||||
src/core/libraries/ime/ime_dialog.cpp
|
||||
|
||||
@ -159,6 +159,8 @@ static ConfigEntry<bool> isMotionControlsEnabled(true);
|
||||
static ConfigEntry<bool> useUnifiedInputConfig(true);
|
||||
static ConfigEntry<string> defaultControllerID("");
|
||||
static ConfigEntry<bool> backgroundControllerInput(false);
|
||||
static ConfigEntry<bool> imeAccessibilityEnabled(false);
|
||||
static ConfigEntry<bool> imeUrlMailShortPanel(false);
|
||||
|
||||
// Audio
|
||||
static ConfigEntry<string> micDevice("Default Device");
|
||||
@ -832,6 +834,22 @@ void setBackgroundControllerInput(bool enable, bool is_game_specific) {
|
||||
backgroundControllerInput.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool getImeAccessibilityEnabled() {
|
||||
return imeAccessibilityEnabled.get();
|
||||
}
|
||||
|
||||
void setImeAccessibilityEnabled(bool enable, bool is_game_specific) {
|
||||
imeAccessibilityEnabled.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool getImeUrlMailShortPanel() {
|
||||
return imeUrlMailShortPanel.get();
|
||||
}
|
||||
|
||||
void setImeUrlMailShortPanel(bool enable, bool is_game_specific) {
|
||||
imeUrlMailShortPanel.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
bool getFsrEnabled() {
|
||||
return fsrEnabled.get();
|
||||
}
|
||||
@ -923,6 +941,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
isMotionControlsEnabled.setFromToml(input, "isMotionControlsEnabled", is_game_specific);
|
||||
useUnifiedInputConfig.setFromToml(input, "useUnifiedInputConfig", is_game_specific);
|
||||
backgroundControllerInput.setFromToml(input, "backgroundControllerInput", is_game_specific);
|
||||
imeAccessibilityEnabled.setFromToml(input, "imeAccessibilityEnabled", is_game_specific);
|
||||
imeUrlMailShortPanel.setFromToml(input, "imeUrlMailShortPanel", is_game_specific);
|
||||
usbDeviceBackend.setFromToml(input, "usbDeviceBackend", is_game_specific);
|
||||
}
|
||||
|
||||
@ -1109,6 +1129,9 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
is_game_specific);
|
||||
backgroundControllerInput.setTomlValue(data, "Input", "backgroundControllerInput",
|
||||
is_game_specific);
|
||||
imeAccessibilityEnabled.setTomlValue(data, "Input", "imeAccessibilityEnabled",
|
||||
is_game_specific);
|
||||
imeUrlMailShortPanel.setTomlValue(data, "Input", "imeUrlMailShortPanel", is_game_specific);
|
||||
usbDeviceBackend.setTomlValue(data, "Input", "usbDeviceBackend", is_game_specific);
|
||||
|
||||
micDevice.setTomlValue(data, "Audio", "micDevice", is_game_specific);
|
||||
|
||||
@ -148,6 +148,10 @@ std::string getDefaultControllerID();
|
||||
void setDefaultControllerID(std::string id);
|
||||
bool getBackgroundControllerInput();
|
||||
void setBackgroundControllerInput(bool enable, bool is_game_specific = false);
|
||||
bool getImeAccessibilityEnabled();
|
||||
void setImeAccessibilityEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getImeUrlMailShortPanel();
|
||||
void setImeUrlMailShortPanel(bool enable, bool is_game_specific = false);
|
||||
bool getLoggingEnabled();
|
||||
void setLoggingEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getFsrEnabled();
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <queue>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/ime/ime.h"
|
||||
#include "core/libraries/ime/ime_dialog.h"
|
||||
#include "core/libraries/ime/ime_error.h"
|
||||
#include "core/libraries/ime/ime_ui.h"
|
||||
#include "core/libraries/libs.h"
|
||||
@ -202,7 +203,8 @@ int PS4_SYSV_ABI sceImeConfigSet() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate() {
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate(s32 index) {
|
||||
(void)index;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -252,7 +254,10 @@ int PS4_SYSV_ABI sceImeForTestFunction() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() {
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(OrbisImePositionAndForm* posForm) {
|
||||
if (!posForm) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -274,45 +279,26 @@ Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u3
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (static_cast<u32>(param->option) & ~0x7BFF) { // Basic check for invalid options
|
||||
LOG_ERROR(Lib_Ime, "Invalid option: {:032b}", static_cast<u32>(param->option));
|
||||
return Error::INVALID_OPTION;
|
||||
}
|
||||
|
||||
switch (param->type) {
|
||||
case OrbisImeType::Default:
|
||||
*width = 500; // dummy value
|
||||
*height = 100; // dummy value
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Default ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::BasicLatin:
|
||||
*width = 500; // dummy value
|
||||
*height = 100; // dummy value
|
||||
LOG_DEBUG(Lib_Ime, "param->type: BasicLatin ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::Url:
|
||||
*width = 500; // dummy value
|
||||
*height = 100; // dummy value
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Url ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::Mail:
|
||||
// We set our custom sizes, commented sizes are the original ones
|
||||
*width = 500; // 793
|
||||
*height = 100; // 408
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Mail ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::Number:
|
||||
*width = 370;
|
||||
*height = 402;
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Number ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_Ime, "Invalid param->type: ({})", static_cast<u32>(param->type));
|
||||
return Error::INVALID_TYPE;
|
||||
}
|
||||
OrbisImeDialogParam dialog_param{};
|
||||
dialog_param.user_id = param->user_id;
|
||||
dialog_param.type = param->type;
|
||||
dialog_param.supported_languages = param->supported_languages;
|
||||
dialog_param.enter_label = param->enter_label;
|
||||
dialog_param.input_method = param->input_method;
|
||||
dialog_param.filter = param->filter;
|
||||
dialog_param.option = param->option;
|
||||
dialog_param.max_text_length = param->maxTextLength;
|
||||
dialog_param.input_text_buffer = param->inputTextBuffer;
|
||||
dialog_param.posx = param->posx;
|
||||
dialog_param.posy = param->posy;
|
||||
dialog_param.horizontal_alignment = param->horizontal_alignment;
|
||||
dialog_param.vertical_alignment = param->vertical_alignment;
|
||||
dialog_param.placeholder = nullptr;
|
||||
dialog_param.title = nullptr;
|
||||
|
||||
const Error ret = Libraries::ImeDialog::sceImeDialogGetPanelSize(&dialog_param, width, height);
|
||||
LOG_DEBUG(Lib_Ime, "IME panel size: width={}, height={}", *width, *height);
|
||||
return Error::OK;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId) {
|
||||
@ -339,7 +325,11 @@ Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceU
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo() {
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo(u32 resourceId, OrbisImeKeyboardInfo* info) {
|
||||
(void)resourceId;
|
||||
if (info) {
|
||||
*info = {};
|
||||
}
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -463,7 +453,10 @@ int PS4_SYSV_ABI sceImeKeyboardOpenInternal() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode() {
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
u32 mode) {
|
||||
(void)userId;
|
||||
(void)mode;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -681,7 +674,8 @@ void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) {
|
||||
param->user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex() {
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex(s32 index) {
|
||||
(void)index;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -714,7 +708,10 @@ Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) {
|
||||
return g_ime_handler->SetText(text, length);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry() {
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry(OrbisImeTextAreaMode mode,
|
||||
const OrbisImeTextGeometry* geometry) {
|
||||
(void)mode;
|
||||
(void)geometry;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ int PS4_SYSV_ABI sceImeCheckUpdateTextInfo();
|
||||
Error PS4_SYSV_ABI sceImeClose();
|
||||
int PS4_SYSV_ABI sceImeConfigGet();
|
||||
int PS4_SYSV_ABI sceImeConfigSet();
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate();
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate(s32 index);
|
||||
int PS4_SYSV_ABI sceImeDicAddWord();
|
||||
int PS4_SYSV_ABI sceImeDicDeleteLearnDics();
|
||||
int PS4_SYSV_ABI sceImeDicDeleteUserDics();
|
||||
@ -31,25 +31,27 @@ int PS4_SYSV_ABI sceImeDicReplaceWord();
|
||||
int PS4_SYSV_ABI sceImeDisableController();
|
||||
int PS4_SYSV_ABI sceImeFilterText();
|
||||
int PS4_SYSV_ABI sceImeForTestFunction();
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm();
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(OrbisImePositionAndForm* posForm);
|
||||
Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height);
|
||||
Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId);
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo();
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo(u32 resourceId, OrbisImeKeyboardInfo* info);
|
||||
Error PS4_SYSV_ABI
|
||||
sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
OrbisImeKeyboardResourceIdArray* resourceIdArray);
|
||||
Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
const OrbisImeKeyboardParam* param);
|
||||
int PS4_SYSV_ABI sceImeKeyboardOpenInternal();
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode();
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
u32 mode);
|
||||
int PS4_SYSV_ABI sceImeKeyboardUpdate();
|
||||
Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended);
|
||||
int PS4_SYSV_ABI sceImeOpenInternal();
|
||||
void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param);
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex();
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex(s32 index);
|
||||
Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret);
|
||||
Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length);
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry();
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry(OrbisImeTextAreaMode mode,
|
||||
const OrbisImeTextGeometry* geometry);
|
||||
Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler);
|
||||
int PS4_SYSV_ABI sceImeVshClearPreedit();
|
||||
int PS4_SYSV_ABI sceImeVshClose();
|
||||
|
||||
@ -375,6 +375,16 @@ enum class OrbisImeKeyboardType : u32 {
|
||||
HUNGARIAN = 37,
|
||||
};
|
||||
|
||||
enum class OrbisImeKeyboardDeviceType : u32 {
|
||||
Keyboard = 0,
|
||||
Osk = 1,
|
||||
};
|
||||
|
||||
enum class OrbisImeKeyboardStatus : u32 {
|
||||
Disconnected = 0,
|
||||
Connected = 1,
|
||||
};
|
||||
|
||||
enum class OrbisImeDeviceType : u32 {
|
||||
None = 0,
|
||||
Controller = 1,
|
||||
@ -438,6 +448,16 @@ struct OrbisImeKeyboardResourceIdArray {
|
||||
u32 resource_id[5];
|
||||
};
|
||||
|
||||
struct OrbisImeKeyboardInfo {
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id;
|
||||
OrbisImeKeyboardDeviceType device;
|
||||
OrbisImeKeyboardType type;
|
||||
u32 repeat_delay;
|
||||
u32 repeat_rate;
|
||||
OrbisImeKeyboardStatus status;
|
||||
s8 reserved[12];
|
||||
};
|
||||
|
||||
enum class OrbisImeCaretMovementDirection : u32 {
|
||||
Still = 0,
|
||||
Left = 1,
|
||||
@ -462,6 +482,23 @@ enum class OrbisImePanelType : u32 {
|
||||
Accessibility = 6,
|
||||
};
|
||||
|
||||
struct OrbisImePositionAndForm {
|
||||
OrbisImePanelType type;
|
||||
f32 posx;
|
||||
f32 posy;
|
||||
OrbisImeHorizontalAlignment horizontal_alignment;
|
||||
OrbisImeVerticalAlignment vertical_alignment;
|
||||
u32 width;
|
||||
u32 height;
|
||||
};
|
||||
|
||||
struct OrbisImeTextGeometry {
|
||||
f32 x;
|
||||
f32 y;
|
||||
u32 width;
|
||||
u32 height;
|
||||
};
|
||||
|
||||
union OrbisImeEventParam {
|
||||
OrbisImeRect rect;
|
||||
OrbisImeEditText text;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,14 +27,14 @@ enum class OrbisImeDialogEndStatus : u32 {
|
||||
|
||||
struct OrbisImeDialogResult {
|
||||
OrbisImeDialogEndStatus endstatus;
|
||||
s32 reserved[12];
|
||||
s8 reserved[12];
|
||||
};
|
||||
|
||||
Error PS4_SYSV_ABI sceImeDialogAbort();
|
||||
Error PS4_SYSV_ABI sceImeDialogForceClose();
|
||||
Error PS4_SYSV_ABI sceImeDialogForTestFunction();
|
||||
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState();
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm();
|
||||
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(s64 param_1);
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(OrbisImePositionAndForm* posForm);
|
||||
Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u32* width,
|
||||
u32* height);
|
||||
Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* param,
|
||||
@ -43,10 +43,12 @@ Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* p
|
||||
Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result);
|
||||
OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus();
|
||||
Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended);
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal();
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal2();
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal3();
|
||||
int PS4_SYSV_ABI sceImeDialogSetPanelPosition();
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal(OrbisImeDialogParam* param,
|
||||
OrbisImeParamExtended* extended);
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal2(int* param_1, u32* param_2, u32 param_3, u64 param_4);
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal3(int* param_1, u32* param_2, u32 param_3, u64 param_4,
|
||||
u32 param_5, u32 param_6);
|
||||
int PS4_SYSV_ABI sceImeDialogSetPanelPosition(s32 posx, s32 posy);
|
||||
Error PS4_SYSV_ABI sceImeDialogTerm();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
@ -1,28 +1,119 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/ime/ime_dialog.h"
|
||||
#include "core/libraries/ime/ime_dialog_ui.h"
|
||||
#include "core/libraries/ime/ime_kb_layout.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/tls.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
using namespace ImGui;
|
||||
|
||||
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
|
||||
|
||||
namespace Libraries::ImeDialog {
|
||||
namespace {
|
||||
bool IsMappedGuestBuffer(const void* ptr, size_t bytes) {
|
||||
if (!ptr || bytes == 0) {
|
||||
return false;
|
||||
}
|
||||
auto* memory = Core::Memory::Instance();
|
||||
if (!memory) {
|
||||
return false;
|
||||
}
|
||||
return memory->IsValidMapping(reinterpret_cast<VAddr>(ptr), bytes);
|
||||
}
|
||||
|
||||
size_t BoundedUtf16Length(const char16_t* text, size_t max_len) {
|
||||
if (!text || max_len == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 0; i < max_len; ++i) {
|
||||
if (text[i] == u'\0') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return max_len;
|
||||
}
|
||||
|
||||
int Utf8CharCount(const char* text, int byte_len) {
|
||||
if (!text || byte_len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return ImTextCountCharsFromUtf8(text, text + byte_len);
|
||||
}
|
||||
|
||||
int Utf8ByteIndexFromCharIndex(const char* text, int char_index) {
|
||||
if (!text || char_index <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const char* p = text;
|
||||
int count = 0;
|
||||
while (*p && count < char_index) {
|
||||
unsigned int c = 0;
|
||||
const int step = ImTextCharFromUtf8(&c, p, nullptr);
|
||||
if (step <= 0) {
|
||||
break;
|
||||
}
|
||||
p += step;
|
||||
++count;
|
||||
}
|
||||
return static_cast<int>(p - text);
|
||||
}
|
||||
|
||||
int Utf16CountFromUtf8Range(const char* text, const char* end) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
const char* range_end = end ? end : (text + std::strlen(text));
|
||||
std::array<char16_t, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1> tmp{};
|
||||
ImTextStrFromUtf8(reinterpret_cast<ImWchar*>(tmp.data()), static_cast<int>(tmp.size()), text,
|
||||
range_end);
|
||||
return static_cast<int>(BoundedUtf16Length(tmp.data(), tmp.size() - 1));
|
||||
}
|
||||
|
||||
int Utf8ByteIndexFromUtf16Index(const char* text, int utf16_index) {
|
||||
if (!text || utf16_index <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const char* p = text;
|
||||
int count = 0;
|
||||
while (*p && count < utf16_index) {
|
||||
unsigned int c = 0;
|
||||
const int step = ImTextCharFromUtf8(&c, p, nullptr);
|
||||
if (step <= 0) {
|
||||
break;
|
||||
}
|
||||
const int utf16_units = (c > 0xFFFF) ? 2 : 1;
|
||||
if (count + utf16_units > utf16_index) {
|
||||
break;
|
||||
}
|
||||
count += utf16_units;
|
||||
p += step;
|
||||
}
|
||||
return static_cast<int>(p - text);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ImeDialogState::ImeDialogState()
|
||||
: input_changed(false), user_id(-1), is_multi_line(false), is_numeric(false),
|
||||
type(OrbisImeType::Default), enter_label(OrbisImeEnterLabel::Default), text_filter(nullptr),
|
||||
keyboard_filter(nullptr), max_text_length(ORBIS_IME_DIALOG_MAX_TEXT_LENGTH),
|
||||
text_buffer(nullptr), title(), placeholder(), current_text() {}
|
||||
text_buffer(nullptr), original_text(), title(), placeholder(), current_text() {}
|
||||
|
||||
ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
const OrbisImeParamExtended* extended) {
|
||||
@ -35,6 +126,7 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
|
||||
user_id = param->user_id;
|
||||
is_multi_line = True(param->option & OrbisImeOption::MULTILINE);
|
||||
use_over2k = True(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES);
|
||||
is_numeric = param->type == OrbisImeType::Number;
|
||||
type = param->type;
|
||||
enter_label = param->enter_label;
|
||||
@ -42,6 +134,21 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
keyboard_filter = extended ? extended->ext_keyboard_filter : nullptr;
|
||||
max_text_length = param->max_text_length;
|
||||
text_buffer = param->input_text_buffer;
|
||||
LOG_INFO(Lib_ImeDialog,
|
||||
"ImeDialogState: option=0x{:X} (multiline={}, password={}, ext_kbd={}, fixed_pos={}, "
|
||||
"over2k={}), enter_label={}, type={}",
|
||||
static_cast<u32>(param->option), is_multi_line,
|
||||
True(param->option & OrbisImeOption::PASSWORD),
|
||||
True(param->option & OrbisImeOption::EXT_KEYBOARD),
|
||||
True(param->option & OrbisImeOption::FIXED_POSITION),
|
||||
True(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES),
|
||||
static_cast<u32>(enter_label), static_cast<u32>(type));
|
||||
LOG_DEBUG(Lib_ImeDialog,
|
||||
"ImeDialogState: user_id={}, type={}, enter_label={}, multiline={}, numeric={}, "
|
||||
"max_len={}, text_filter={}, keyboard_filter={}",
|
||||
static_cast<u32>(user_id), static_cast<u32>(type), static_cast<u32>(enter_label),
|
||||
is_multi_line, is_numeric, max_text_length, (void*)text_filter,
|
||||
(void*)keyboard_filter);
|
||||
|
||||
if (param->title) {
|
||||
std::size_t title_len = std::char_traits<char16_t>::length(param->title);
|
||||
@ -64,20 +171,56 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
}
|
||||
}
|
||||
|
||||
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 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
|
||||
std::size_t text_len = 0;
|
||||
if (text_buffer) {
|
||||
const size_t bytes = (static_cast<size_t>(max_text_length) + 1) * sizeof(char16_t);
|
||||
if (IsMappedGuestBuffer(text_buffer, bytes)) {
|
||||
text_len = BoundedUtf16Length(text_buffer, max_text_length);
|
||||
} else {
|
||||
LOG_ERROR(Lib_ImeDialog, "ImeDialogState: input_text_buffer not mapped");
|
||||
}
|
||||
}
|
||||
if (text_len > max_text_length) {
|
||||
text_len = max_text_length;
|
||||
}
|
||||
original_text.resize(static_cast<std::size_t>(max_text_length) + 1, u'\0');
|
||||
if (text_buffer) {
|
||||
for (std::size_t i = 0; i < text_len; ++i) {
|
||||
original_text[i] = text_buffer[i];
|
||||
}
|
||||
}
|
||||
if (text_buffer) {
|
||||
if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
|
||||
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
|
||||
}
|
||||
}
|
||||
caret_index = Utf16CountFromUtf8Range(
|
||||
current_text.begin(), current_text.begin() + static_cast<int>(current_text.size()));
|
||||
caret_byte_index = static_cast<int>(current_text.size());
|
||||
caret_dirty = true;
|
||||
panel_layout_valid = (sceImeDialogGetPanelPositionAndForm(&panel_layout) == ORBIS_OK);
|
||||
if (extended) {
|
||||
(void)sceImeDialogGetPanelSizeExtended(param, extended, &panel_req_width,
|
||||
&panel_req_height);
|
||||
} else {
|
||||
(void)sceImeDialogGetPanelSize(param, &panel_req_width, &panel_req_height);
|
||||
}
|
||||
LOG_DEBUG(Lib_ImeDialog, "ImeDialogState: initial_text_len={}", current_text.size());
|
||||
}
|
||||
|
||||
ImeDialogState::ImeDialogState(ImeDialogState&& other) noexcept
|
||||
: input_changed(other.input_changed), user_id(other.user_id),
|
||||
: input_changed(other.input_changed), caret_index(other.caret_index),
|
||||
caret_byte_index(other.caret_byte_index), caret_dirty(other.caret_dirty),
|
||||
use_over2k(other.use_over2k), panel_layout(other.panel_layout),
|
||||
panel_layout_valid(other.panel_layout_valid), panel_req_width(other.panel_req_width),
|
||||
panel_req_height(other.panel_req_height), user_id(other.user_id),
|
||||
is_multi_line(other.is_multi_line), 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) {
|
||||
text_buffer(other.text_buffer), original_text(std::move(other.original_text)),
|
||||
title(std::move(other.title)), placeholder(std::move(other.placeholder)),
|
||||
current_text(other.current_text) {
|
||||
|
||||
other.text_buffer = nullptr;
|
||||
}
|
||||
@ -85,6 +228,14 @@ ImeDialogState::ImeDialogState(ImeDialogState&& other) noexcept
|
||||
ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) {
|
||||
if (this != &other) {
|
||||
input_changed = other.input_changed;
|
||||
caret_index = other.caret_index;
|
||||
caret_byte_index = other.caret_byte_index;
|
||||
caret_dirty = other.caret_dirty;
|
||||
use_over2k = other.use_over2k;
|
||||
panel_layout = other.panel_layout;
|
||||
panel_layout_valid = other.panel_layout_valid;
|
||||
panel_req_width = other.panel_req_width;
|
||||
panel_req_height = other.panel_req_height;
|
||||
user_id = other.user_id;
|
||||
is_multi_line = other.is_multi_line;
|
||||
is_numeric = other.is_numeric;
|
||||
@ -94,6 +245,7 @@ ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) {
|
||||
keyboard_filter = other.keyboard_filter;
|
||||
max_text_length = other.max_text_length;
|
||||
text_buffer = other.text_buffer;
|
||||
original_text = std::move(other.original_text);
|
||||
title = std::move(other.title);
|
||||
placeholder = std::move(other.placeholder);
|
||||
current_text = other.current_text;
|
||||
@ -104,13 +256,76 @@ ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool ImeDialogState::CopyTextToOrbisBuffer() {
|
||||
bool ImeDialogState::CopyTextToOrbisBuffer(bool use_original) {
|
||||
if (!text_buffer) {
|
||||
LOG_DEBUG(Lib_ImeDialog, "CopyTextToOrbisBuffer: no text_buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer,
|
||||
static_cast<std::size_t>(max_text_length) + 1);
|
||||
if (use_original) {
|
||||
const std::size_t count =
|
||||
original_text.empty() ? 0 : static_cast<std::size_t>(max_text_length) + 1;
|
||||
if (count > 0) {
|
||||
std::copy(original_text.begin(), original_text.end(), text_buffer);
|
||||
}
|
||||
LOG_DEBUG(Lib_ImeDialog, "CopyTextToOrbisBuffer: restored original");
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::size_t utf8_len = current_text.size();
|
||||
const bool ok = ConvertUTF8ToOrbis(current_text.begin(), utf8_len, text_buffer,
|
||||
static_cast<std::size_t>(max_text_length) + 1);
|
||||
LOG_DEBUG(Lib_ImeDialog, "CopyTextToOrbisBuffer: {}", ok ? "ok" : "failed");
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool ImeDialogState::NormalizeNewlines() {
|
||||
if (current_text.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
std::string src = current_text.to_string();
|
||||
std::string out;
|
||||
out.reserve(src.size());
|
||||
bool changed = false;
|
||||
for (size_t i = 0; i < src.size(); ++i) {
|
||||
const char ch = src[i];
|
||||
if (ch == '\r') {
|
||||
if (i + 1 < src.size() && src[i + 1] == '\n') {
|
||||
++i;
|
||||
}
|
||||
out.push_back('\n');
|
||||
changed = true;
|
||||
} else {
|
||||
out.push_back(ch);
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
current_text.FromString(out);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool ImeDialogState::ClampCurrentTextToMaxLen() {
|
||||
if (current_text.size() == 0 || max_text_length == 0) {
|
||||
return false;
|
||||
}
|
||||
const int utf16_len = Utf16CountFromUtf8Range(
|
||||
current_text.begin(), current_text.begin() + static_cast<int>(current_text.size()));
|
||||
if (utf16_len <= static_cast<int>(max_text_length)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<char16_t> utf16(static_cast<size_t>(max_text_length) + 1, u'\0');
|
||||
ImTextStrFromUtf8(reinterpret_cast<ImWchar*>(utf16.data()),
|
||||
static_cast<int>(max_text_length) + 1, current_text.begin(),
|
||||
current_text.begin() + current_text.size());
|
||||
size_t len = BoundedUtf16Length(utf16.data(), static_cast<size_t>(max_text_length));
|
||||
std::string out;
|
||||
out.resize(len * 4 + 1, '\0');
|
||||
ImTextStrToUtf8(out.data(), out.size(), reinterpret_cast<const ImWchar*>(utf16.data()),
|
||||
reinterpret_cast<const ImWchar*>(utf16.data()) + len);
|
||||
out.resize(std::strlen(out.c_str()));
|
||||
current_text.FromString(out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImeDialogState::CallTextFilter() {
|
||||
@ -121,15 +336,17 @@ bool ImeDialogState::CallTextFilter() {
|
||||
input_changed = false;
|
||||
|
||||
char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0};
|
||||
u32 src_text_length = current_text.size();
|
||||
u32 src_text_length = 0;
|
||||
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)) {
|
||||
if (!ConvertUTF8ToOrbis(current_text.begin(), current_text.size(), src_text,
|
||||
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to orbis encoding");
|
||||
return false;
|
||||
}
|
||||
src_text_length = static_cast<u32>(
|
||||
BoundedUtf16Length(src_text, static_cast<size_t>(ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)));
|
||||
|
||||
int ret = text_filter(out_text, &out_text_length, src_text, src_text_length);
|
||||
|
||||
@ -143,6 +360,17 @@ bool ImeDialogState::CallTextFilter() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool changed = NormalizeNewlines() | ClampCurrentTextToMaxLen();
|
||||
const int new_len = Utf16CountFromUtf8Range(
|
||||
current_text.begin(), current_text.begin() + static_cast<int>(current_text.size()));
|
||||
if (caret_index > new_len) {
|
||||
caret_index = new_len;
|
||||
caret_dirty = true;
|
||||
} else if (changed) {
|
||||
caret_dirty = true;
|
||||
}
|
||||
|
||||
CopyTextToOrbisBuffer(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -168,7 +396,8 @@ bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t
|
||||
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);
|
||||
const char* utf8_end = utf8_text ? (utf8_text + utf8_text_len) : nullptr;
|
||||
ImTextStrFromUtf8(reinterpret_cast<ImWchar*>(orbis_text), orbis_text_len, utf8_text, utf8_end);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -225,6 +454,18 @@ void ImeDialogUi::Free() {
|
||||
RemoveLayer(this);
|
||||
}
|
||||
|
||||
void ImeDialogUi::FinishDialog(OrbisImeDialogEndStatus endstatus, bool restore_original,
|
||||
const char* reason) {
|
||||
if (!status || !result || !state) {
|
||||
return;
|
||||
}
|
||||
state->CopyTextToOrbisBuffer(restore_original);
|
||||
*status = OrbisImeDialogStatus::Finished;
|
||||
result->endstatus = endstatus;
|
||||
LOG_INFO(Lib_ImeDialog, "ImeDialog {} -> status=Finished", reason ? reason : "Done");
|
||||
Free();
|
||||
}
|
||||
|
||||
void ImeDialogUi::Draw() {
|
||||
std::unique_lock<std::mutex> lock{draw_mutex};
|
||||
|
||||
@ -233,21 +474,67 @@ void ImeDialogUi::Draw() {
|
||||
}
|
||||
|
||||
if (!status || *status != OrbisImeDialogStatus::Running) {
|
||||
Free();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& ctx = *GetCurrentContext();
|
||||
const auto& io = ctx.IO;
|
||||
|
||||
ImVec2 window_size;
|
||||
constexpr int key_cols = 10;
|
||||
constexpr int key_rows = 6;
|
||||
OrbisImePositionAndForm layout = state->panel_layout;
|
||||
const bool has_layout = state->panel_layout_valid;
|
||||
const auto viewport = Libraries::Ime::ComputeImeViewportMetrics(state->use_over2k);
|
||||
const ImVec2 viewport_size = viewport.size;
|
||||
const ImVec2 viewport_offset = viewport.offset;
|
||||
const float scale_x = viewport.scale_x;
|
||||
const float scale_y = viewport.scale_y;
|
||||
const float ui_scale = viewport.ui_scale;
|
||||
|
||||
if (state->is_multi_line) {
|
||||
window_size = {500.0f, 300.0f};
|
||||
ImVec2 window_size;
|
||||
const bool has_panel_size = (has_layout && layout.width > 0 && layout.height > 0) ||
|
||||
(state->panel_req_width > 0 && state->panel_req_height > 0);
|
||||
if (has_layout && layout.width > 0 && layout.height > 0) {
|
||||
window_size = {layout.width * scale_x, layout.height * scale_y};
|
||||
} else if (state->panel_req_width > 0 && state->panel_req_height > 0) {
|
||||
window_size = {static_cast<float>(state->panel_req_width) * scale_x,
|
||||
static_cast<float>(state->panel_req_height) * scale_y};
|
||||
} else {
|
||||
window_size = {500.0f, 150.0f};
|
||||
window_size = {std::min(std::max(0.0f, viewport_size.x - 40.0f), 640.0f),
|
||||
std::min(std::max(0.0f, viewport_size.y - 40.0f), 420.0f)};
|
||||
}
|
||||
if (!has_panel_size) {
|
||||
window_size.x = std::max(window_size.x, 320.0f);
|
||||
window_size.y = std::max(window_size.y, 240.0f);
|
||||
}
|
||||
|
||||
CentralizeNextWindow();
|
||||
const float panel_w = window_size.x;
|
||||
const float panel_h = window_size.y;
|
||||
|
||||
if (has_layout) {
|
||||
float x = viewport_offset.x + layout.posx * scale_x;
|
||||
float y = viewport_offset.y + layout.posy * scale_y;
|
||||
if (layout.horizontal_alignment == OrbisImeHorizontalAlignment::Center) {
|
||||
x -= window_size.x * 0.5f;
|
||||
} else if (layout.horizontal_alignment == OrbisImeHorizontalAlignment::Right) {
|
||||
x -= window_size.x;
|
||||
}
|
||||
if (layout.vertical_alignment == OrbisImeVerticalAlignment::Center) {
|
||||
y -= window_size.y * 0.5f;
|
||||
} else if (layout.vertical_alignment == OrbisImeVerticalAlignment::Bottom) {
|
||||
y -= window_size.y;
|
||||
}
|
||||
x = std::clamp(x, viewport_offset.x,
|
||||
viewport_offset.x + std::max(0.0f, viewport_size.x - window_size.x));
|
||||
y = std::clamp(y, viewport_offset.y,
|
||||
viewport_offset.y + std::max(0.0f, viewport_size.y - window_size.y));
|
||||
SetNextWindowPos({x, y});
|
||||
} else {
|
||||
SetNextWindowPos({viewport_offset.x + viewport_size.x * 0.5f,
|
||||
viewport_offset.y + viewport_size.y * 0.5f},
|
||||
ImGuiCond_Always, {0.5f, 0.5f});
|
||||
}
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
|
||||
@ -258,80 +545,153 @@ void ImeDialogUi::Draw() {
|
||||
if (Begin("IME Dialog##ImeDialog", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||
DrawPrettyBackground();
|
||||
const Libraries::Ime::ImePanelMetricsConfig metrics_cfg{
|
||||
.panel_w = panel_w,
|
||||
.panel_h = panel_h,
|
||||
.multiline = state->is_multi_line,
|
||||
.show_title = !state->title.empty(),
|
||||
.base_font_size = GetFontSize(),
|
||||
.window_pos = GetWindowPos(),
|
||||
};
|
||||
const Libraries::Ime::ImePanelMetrics metrics =
|
||||
Libraries::Ime::ComputeImePanelMetrics(metrics_cfg);
|
||||
SetWindowFontScale(std::max(ui_scale, metrics.input_font_scale));
|
||||
|
||||
if (first_render) {
|
||||
const auto game_res = DebugState.game_resolution;
|
||||
const auto out_res = DebugState.output_resolution;
|
||||
const float req_w = has_layout ? static_cast<float>(layout.width)
|
||||
: static_cast<float>(state->panel_req_width);
|
||||
const float req_h = has_layout ? static_cast<float>(layout.height)
|
||||
: static_cast<float>(state->panel_req_height);
|
||||
LOG_INFO(Lib_ImeDialog,
|
||||
"ImeDialog UI metrics: game_res={}x{}, out_res={}x{}, viewport_pos=({}, {}), "
|
||||
"viewport_size=({}, {}), base={}x{}, scale=({:.4f}, {:.4f}), "
|
||||
"panel_req=({}, {}), panel_scaled=({}, {})",
|
||||
game_res.first, game_res.second, out_res.first, out_res.second,
|
||||
viewport_offset.x, viewport_offset.y, viewport_size.x, viewport_size.y,
|
||||
viewport.base_w, viewport.base_h, scale_x, scale_y, req_w, req_h,
|
||||
window_size.x, window_size.y);
|
||||
}
|
||||
|
||||
if (!state->title.empty()) {
|
||||
SetWindowFontScale(1.7f);
|
||||
SetCursorPosY(0.0f);
|
||||
SetCursorPosX(metrics.padding_x);
|
||||
SetWindowFontScale(metrics.label_font_scale);
|
||||
TextUnformatted(state->title.data());
|
||||
SetWindowFontScale(1.0f);
|
||||
SetWindowFontScale(std::max(ui_scale, metrics.input_font_scale));
|
||||
}
|
||||
|
||||
if (state->is_multi_line) {
|
||||
DrawMultiLineInputText();
|
||||
DrawMultiLineInputText(metrics);
|
||||
} else {
|
||||
DrawInputText();
|
||||
DrawInputText(metrics);
|
||||
}
|
||||
|
||||
SetCursorPosY(GetCursorPosY() + 10.0f);
|
||||
auto* draw = GetWindowDrawList();
|
||||
const ImU32 pane_bg = IM_COL32(18, 18, 18, 255);
|
||||
const ImU32 pane_border = IM_COL32(70, 70, 70, 255);
|
||||
draw->AddRectFilled(metrics.predict_pos,
|
||||
{metrics.predict_pos.x + metrics.predict_size.x,
|
||||
metrics.predict_pos.y + metrics.predict_size.y},
|
||||
pane_bg, metrics.corner_radius);
|
||||
draw->AddRect(metrics.predict_pos,
|
||||
{metrics.predict_pos.x + metrics.predict_size.x,
|
||||
metrics.predict_pos.y + metrics.predict_size.y},
|
||||
pane_border, metrics.corner_radius);
|
||||
PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.15f, 1.0f));
|
||||
PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.25f, 0.25f, 1.0f));
|
||||
PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.35f, 0.35f, 0.35f, 1.0f));
|
||||
SetCursorScreenPos(metrics.close_pos);
|
||||
const bool cancel_pressed =
|
||||
Button("X##ImeDialogClose", {metrics.close_size.x, metrics.close_size.y});
|
||||
PopStyleColor(3);
|
||||
|
||||
const char* button_text;
|
||||
SetCursorScreenPos(metrics.kb_pos);
|
||||
|
||||
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;
|
||||
if (!accept_armed) {
|
||||
if (!IsKeyDown(ImGuiKey_Enter) && !IsKeyDown(ImGuiKey_GamepadFaceDown)) {
|
||||
accept_armed = true;
|
||||
LOG_DEBUG(Lib_ImeDialog, "ImeDialog: accept armed");
|
||||
}
|
||||
}
|
||||
const bool allow_key_accept = accept_armed;
|
||||
|
||||
bool accept_pressed =
|
||||
(allow_key_accept && !state->is_multi_line && IsKeyPressed(ImGuiKey_Enter)) ||
|
||||
(allow_key_accept && IsKeyPressed(ImGuiKey_GamepadFaceDown));
|
||||
|
||||
Libraries::Ime::ImeKbGridLayout kb_layout{};
|
||||
kb_layout.pos = metrics.kb_pos;
|
||||
kb_layout.size = metrics.kb_size;
|
||||
kb_layout.key_gap_x = metrics.key_gap;
|
||||
kb_layout.key_gap_y = metrics.key_gap;
|
||||
kb_layout.key_h = metrics.key_h;
|
||||
kb_layout.cols = key_cols;
|
||||
kb_layout.rows = key_rows;
|
||||
kb_layout.corner_radius = metrics.corner_radius;
|
||||
|
||||
Libraries::Ime::ImeKbDrawParams kb_params{};
|
||||
kb_params.enter_label = state->enter_label;
|
||||
kb_params.key_bg_alt = IM_COL32(45, 45, 45, 255);
|
||||
|
||||
Libraries::Ime::ImeKbDrawState kb_state{};
|
||||
SetWindowFontScale(metrics.key_font_scale);
|
||||
Libraries::Ime::DrawImeKeyboardGrid(kb_layout, kb_params, kb_state);
|
||||
SetWindowFontScale(metrics.input_font_scale);
|
||||
if (kb_state.done_pressed) {
|
||||
accept_pressed = true;
|
||||
}
|
||||
|
||||
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;
|
||||
Dummy({metrics.kb_size.x, metrics.kb_size.y + metrics.padding_bottom});
|
||||
|
||||
SetCursorPosX(button_start_pos);
|
||||
|
||||
if (Button(button_text, BUTTON_SIZE) ||
|
||||
(!state->is_multi_line && 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::UserCanceled;
|
||||
if (accept_pressed) {
|
||||
LOG_INFO(Lib_ImeDialog, "ImeDialog OK text(len={}): \"{}\"", state->current_text.size(),
|
||||
state->current_text.begin());
|
||||
FinishDialog(OrbisImeDialogEndStatus::Ok, false, "OK");
|
||||
} else if (cancel_pressed) {
|
||||
FinishDialog(OrbisImeDialogEndStatus::UserCanceled, true, "Cancel");
|
||||
}
|
||||
SetWindowFontScale(1.0f);
|
||||
}
|
||||
End();
|
||||
|
||||
first_render = false;
|
||||
}
|
||||
|
||||
void ImeDialogUi::DrawInputText() {
|
||||
ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f};
|
||||
SetCursorPosX(20.0f);
|
||||
void ImeDialogUi::DrawInputText(const Libraries::Ime::ImePanelMetrics& metrics) {
|
||||
const ImVec2 input_size = metrics.input_size;
|
||||
SetCursorPos(metrics.input_pos_local);
|
||||
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 * 4 + 1, input_size,
|
||||
ImGuiInputTextFlags_CallbackCharFilter, InputTextCallback, this)) {
|
||||
ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_CallbackAlways,
|
||||
InputTextCallback, this)) {
|
||||
state->input_changed = true;
|
||||
const bool changed = state->NormalizeNewlines() | state->ClampCurrentTextToMaxLen();
|
||||
if (changed) {
|
||||
const int buf_len = static_cast<int>(state->current_text.size());
|
||||
const int caret_byte = std::clamp(state->caret_byte_index, 0, buf_len);
|
||||
state->caret_index = Utf16CountFromUtf8Range(state->current_text.begin(),
|
||||
state->current_text.begin() + caret_byte);
|
||||
const int new_len = Utf16CountFromUtf8Range(
|
||||
state->current_text.begin(),
|
||||
state->current_text.begin() + static_cast<int>(state->current_text.size()));
|
||||
if (state->caret_index > new_len) {
|
||||
state->caret_index = new_len;
|
||||
}
|
||||
state->caret_dirty = true;
|
||||
}
|
||||
state->CopyTextToOrbisBuffer(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ImeDialogUi::DrawMultiLineInputText() {
|
||||
ImVec2 input_size = {GetWindowWidth() - 40.0f, 200.0f};
|
||||
SetCursorPosX(20.0f);
|
||||
void ImeDialogUi::DrawMultiLineInputText(const Libraries::Ime::ImePanelMetrics& metrics) {
|
||||
const ImVec2 input_size = metrics.input_size;
|
||||
SetCursorPos(metrics.input_pos_local);
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackCharFilter |
|
||||
static_cast<ImGuiInputTextFlags>(ImGuiInputTextFlags_Multiline);
|
||||
if (first_render) {
|
||||
@ -339,8 +699,24 @@ void ImeDialogUi::DrawMultiLineInputText() {
|
||||
}
|
||||
const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data();
|
||||
if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(),
|
||||
state->max_text_length * 4 + 1, input_size, flags, InputTextCallback, this)) {
|
||||
state->max_text_length * 4 + 1, input_size,
|
||||
flags | ImGuiInputTextFlags_CallbackAlways, InputTextCallback, this)) {
|
||||
state->input_changed = true;
|
||||
const bool changed = state->ClampCurrentTextToMaxLen();
|
||||
if (changed) {
|
||||
const int buf_len = static_cast<int>(state->current_text.size());
|
||||
const int caret_byte = std::clamp(state->caret_byte_index, 0, buf_len);
|
||||
state->caret_index = Utf16CountFromUtf8Range(state->current_text.begin(),
|
||||
state->current_text.begin() + caret_byte);
|
||||
const int new_len = Utf16CountFromUtf8Range(
|
||||
state->current_text.begin(),
|
||||
state->current_text.begin() + static_cast<int>(state->current_text.size()));
|
||||
if (state->caret_index > new_len) {
|
||||
state->caret_index = new_len;
|
||||
}
|
||||
state->caret_dirty = true;
|
||||
}
|
||||
state->CopyTextToOrbisBuffer(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,6 +724,31 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
||||
ImeDialogUi* ui = static_cast<ImeDialogUi*>(data->UserData);
|
||||
ASSERT(ui);
|
||||
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackAlways) {
|
||||
if (data->BufTextLen > 0) {
|
||||
const int caret_utf16 = Utf16CountFromUtf8Range(data->Buf, data->Buf + data->CursorPos);
|
||||
LOG_DEBUG(Lib_ImeDialog, "ImeDialog caret: buf_len={}, cursor_byte={}, caret_utf16={}",
|
||||
data->BufTextLen, data->CursorPos, caret_utf16);
|
||||
}
|
||||
if (ui->state->caret_dirty) {
|
||||
const int len_chars = Utf16CountFromUtf8Range(data->Buf, data->Buf + data->BufTextLen);
|
||||
int caret = ui->state->caret_index;
|
||||
if (caret < 0) {
|
||||
caret = 0;
|
||||
} else if (caret > len_chars) {
|
||||
caret = len_chars;
|
||||
}
|
||||
const int caret_byte = Utf8ByteIndexFromUtf16Index(data->Buf, caret);
|
||||
data->CursorPos = caret_byte;
|
||||
data->SelectionStart = caret_byte;
|
||||
data->SelectionEnd = caret_byte;
|
||||
ui->state->caret_dirty = false;
|
||||
}
|
||||
ui->state->caret_byte_index = data->CursorPos;
|
||||
ui->state->caret_index = Utf16CountFromUtf8Range(data->Buf, data->Buf + data->CursorPos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Lib_ImeDialog, ">> InputTextCallback: EventFlag={}, EventChar={}", data->EventFlag,
|
||||
data->EventChar);
|
||||
|
||||
@ -359,10 +760,18 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ui->state->is_multi_line && (data->EventChar == '\n' || data->EventChar == '\r')) {
|
||||
const int caret_utf16 = Utf16CountFromUtf8Range(data->Buf, data->Buf + data->CursorPos);
|
||||
ui->state->caret_index = caret_utf16 + 1;
|
||||
ui->state->caret_dirty = true;
|
||||
}
|
||||
|
||||
if (!ui->state->keyboard_filter) {
|
||||
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: no keyboard_filter, accepting char");
|
||||
return 0;
|
||||
}
|
||||
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: skipping keyboard_filter on render thread");
|
||||
return 0;
|
||||
|
||||
// ImGui encodes ImWchar32 as multi-byte UTF-8 characters
|
||||
char* event_char = reinterpret_cast<char*>(&data->EventChar);
|
||||
@ -398,6 +807,10 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
||||
keep ? "true" : "false", out_keycode, out_status);
|
||||
// TODO. set the keycode
|
||||
|
||||
if (!keep) {
|
||||
LOG_INFO(Lib_ImeDialog, "InputTextCallback: keyboard_filter rejected char");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -14,11 +14,26 @@
|
||||
namespace Libraries::ImeDialog {
|
||||
|
||||
class ImeDialogUi;
|
||||
} // namespace Libraries::ImeDialog
|
||||
|
||||
namespace Libraries::Ime {
|
||||
struct ImePanelMetrics;
|
||||
}
|
||||
|
||||
namespace Libraries::ImeDialog {
|
||||
|
||||
class ImeDialogState final {
|
||||
friend ImeDialogUi;
|
||||
|
||||
bool input_changed = false;
|
||||
int caret_index = 0;
|
||||
int caret_byte_index = 0;
|
||||
bool caret_dirty = false;
|
||||
bool use_over2k = false;
|
||||
OrbisImePositionAndForm panel_layout{};
|
||||
bool panel_layout_valid = false;
|
||||
u32 panel_req_width = 0;
|
||||
u32 panel_req_height = 0;
|
||||
|
||||
s32 user_id{};
|
||||
bool is_multi_line{};
|
||||
@ -29,6 +44,7 @@ class ImeDialogState final {
|
||||
OrbisImeExtKeyboardFilter keyboard_filter{};
|
||||
u32 max_text_length{};
|
||||
char16_t* text_buffer{};
|
||||
std::vector<char16_t> original_text;
|
||||
std::vector<char> title;
|
||||
std::vector<char> placeholder;
|
||||
|
||||
@ -48,8 +64,10 @@ public:
|
||||
ImeDialogState(ImeDialogState&& other) noexcept;
|
||||
ImeDialogState& operator=(ImeDialogState&& other);
|
||||
|
||||
bool CopyTextToOrbisBuffer();
|
||||
bool CopyTextToOrbisBuffer(bool use_original);
|
||||
bool CallTextFilter();
|
||||
bool NormalizeNewlines();
|
||||
bool ClampCurrentTextToMaxLen();
|
||||
|
||||
private:
|
||||
bool CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status);
|
||||
@ -66,6 +84,7 @@ class ImeDialogUi final : public ImGui::Layer {
|
||||
OrbisImeDialogResult* result{};
|
||||
|
||||
bool first_render = true;
|
||||
bool accept_armed = false;
|
||||
std::mutex draw_mutex;
|
||||
|
||||
public:
|
||||
@ -79,10 +98,11 @@ public:
|
||||
void Draw() override;
|
||||
|
||||
private:
|
||||
void FinishDialog(OrbisImeDialogEndStatus endstatus, bool restore_original, const char* reason);
|
||||
void Free();
|
||||
|
||||
void DrawInputText();
|
||||
void DrawMultiLineInputText();
|
||||
void DrawInputText(const Libraries::Ime::ImePanelMetrics& metrics);
|
||||
void DrawMultiLineInputText(const Libraries::Ime::ImePanelMetrics& metrics);
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData* data);
|
||||
};
|
||||
|
||||
237
src/core/libraries/ime/ime_kb_layout.cpp
Normal file
237
src/core/libraries/ime/ime_kb_layout.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/libraries/ime/ime_kb_layout.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "core/debug_state.h"
|
||||
|
||||
namespace Libraries::Ime {
|
||||
namespace {
|
||||
struct KeySpec {
|
||||
int span;
|
||||
const char* label;
|
||||
bool is_done;
|
||||
};
|
||||
|
||||
constexpr float kPanelBaseW = 793.0f;
|
||||
constexpr float kPanelBaseHSingle = 528.0f;
|
||||
constexpr float kPanelBaseHMulti = 628.0f;
|
||||
constexpr float kLabelH = 57.0f;
|
||||
constexpr float kInputHSingle = 50.0f;
|
||||
constexpr float kInputHMulti = 151.0f;
|
||||
constexpr float kPredictH = 53.0f;
|
||||
constexpr float kPredictW = 740.0f;
|
||||
constexpr float kCloseW = 53.0f;
|
||||
constexpr float kKeysH = 316.0f;
|
||||
constexpr float kKeyGap = 9.0f;
|
||||
constexpr float kPadX = 26.0f;
|
||||
constexpr float kPadBottomSingle = 26.0f;
|
||||
constexpr float kPadBottomMulti = 26.0f;
|
||||
constexpr float kKeyFontRatio = 0.035f;
|
||||
constexpr float kCornerRatio = 0.004f;
|
||||
constexpr float kSingleLineTextFill = 0.85f;
|
||||
constexpr float kMultiLineTextFill = 0.85f;
|
||||
constexpr int kMultiLineVisibleLines = 4;
|
||||
constexpr int kKeyRows = 6;
|
||||
constexpr int kKeyCols = 10;
|
||||
|
||||
const char* GetEnterLabel(OrbisImeEnterLabel label) {
|
||||
switch (label) {
|
||||
case OrbisImeEnterLabel::Go:
|
||||
return "Go";
|
||||
case OrbisImeEnterLabel::Search:
|
||||
return "Search";
|
||||
case OrbisImeEnterLabel::Send:
|
||||
return "Send";
|
||||
case OrbisImeEnterLabel::Default:
|
||||
default:
|
||||
return "Done";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ImeViewportMetrics ComputeImeViewportMetrics(bool use_over2k) {
|
||||
ImeViewportMetrics metrics{};
|
||||
metrics.base_w = use_over2k ? 3840.0f : 1920.0f;
|
||||
metrics.base_h = use_over2k ? 2160.0f : 1080.0f;
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImVec2 base_pos = viewport ? viewport->WorkPos : ImVec2{0.0f, 0.0f};
|
||||
ImVec2 base_size = viewport ? viewport->WorkSize : io.DisplaySize;
|
||||
if (base_size.x <= 0.0f || base_size.y <= 0.0f) {
|
||||
base_pos = {0.0f, 0.0f};
|
||||
base_size = io.DisplaySize;
|
||||
}
|
||||
|
||||
metrics.size = base_size;
|
||||
metrics.offset = base_pos;
|
||||
|
||||
const auto out_res = DebugState.output_resolution;
|
||||
if (out_res.first != 0 && out_res.second != 0) {
|
||||
const float fb_scale_x =
|
||||
io.DisplayFramebufferScale.x > 0.0f ? io.DisplayFramebufferScale.x : 1.0f;
|
||||
const float fb_scale_y =
|
||||
io.DisplayFramebufferScale.y > 0.0f ? io.DisplayFramebufferScale.y : 1.0f;
|
||||
const float viewport_w = static_cast<float>(out_res.first) / fb_scale_x;
|
||||
const float viewport_h = static_cast<float>(out_res.second) / fb_scale_y;
|
||||
|
||||
float offset_x = (base_size.x - viewport_w) * 0.5f;
|
||||
float offset_y = (base_size.y - viewport_h) * 0.5f;
|
||||
if (offset_x < 0.0f) {
|
||||
offset_x = 0.0f;
|
||||
}
|
||||
if (offset_y < 0.0f) {
|
||||
offset_y = 0.0f;
|
||||
}
|
||||
|
||||
metrics.size = {viewport_w, viewport_h};
|
||||
metrics.offset = {base_pos.x + offset_x, base_pos.y + offset_y};
|
||||
}
|
||||
|
||||
metrics.scale_x = metrics.size.x / metrics.base_w;
|
||||
metrics.scale_y = metrics.size.y / metrics.base_h;
|
||||
metrics.ui_scale = std::min(metrics.scale_x, metrics.scale_y);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
ImePanelMetrics ComputeImePanelMetrics(const ImePanelMetricsConfig& config) {
|
||||
ImePanelMetrics metrics{};
|
||||
metrics.panel_w = config.panel_w;
|
||||
metrics.panel_h = config.panel_h;
|
||||
metrics.padding_x = metrics.panel_w * (kPadX / kPanelBaseW);
|
||||
metrics.padding_bottom =
|
||||
metrics.panel_h * (config.multiline ? (kPadBottomMulti / kPanelBaseHMulti)
|
||||
: (kPadBottomSingle / kPanelBaseHSingle));
|
||||
|
||||
const float panel_scale = metrics.panel_w / kPanelBaseW;
|
||||
metrics.label_h = config.show_title ? (kLabelH * panel_scale) : metrics.padding_bottom;
|
||||
metrics.input_h = metrics.panel_h * (config.multiline ? (kInputHMulti / kPanelBaseHMulti)
|
||||
: (kInputHSingle / kPanelBaseHSingle));
|
||||
metrics.predict_h = metrics.panel_h * (config.multiline ? (kPredictH / kPanelBaseHMulti)
|
||||
: (kPredictH / kPanelBaseHSingle));
|
||||
metrics.close_w = metrics.panel_w * (kCloseW / kPanelBaseW);
|
||||
metrics.keys_h = metrics.panel_h * (config.multiline ? (kKeysH / kPanelBaseHMulti)
|
||||
: (kKeysH / kPanelBaseHSingle));
|
||||
metrics.key_gap = metrics.panel_w * (kKeyGap / kPanelBaseW);
|
||||
metrics.corner_radius = metrics.panel_w * kCornerRatio;
|
||||
|
||||
const float base_font_size = config.base_font_size > 0.0f ? config.base_font_size : 1.0f;
|
||||
metrics.label_font_scale = (metrics.label_h * 0.85f) / base_font_size;
|
||||
metrics.input_font_scale =
|
||||
(metrics.input_h * (config.multiline
|
||||
? (kMultiLineTextFill / static_cast<float>(kMultiLineVisibleLines))
|
||||
: kSingleLineTextFill)) /
|
||||
base_font_size;
|
||||
metrics.key_font_scale = (metrics.panel_h * kKeyFontRatio) / base_font_size;
|
||||
|
||||
metrics.input_pos_local = {metrics.padding_x, metrics.label_h};
|
||||
metrics.input_size = {metrics.panel_w - metrics.padding_x * 2.0f, metrics.input_h};
|
||||
metrics.input_pos_screen = {config.window_pos.x + metrics.input_pos_local.x,
|
||||
config.window_pos.y + metrics.input_pos_local.y};
|
||||
|
||||
const float remaining_gap =
|
||||
metrics.panel_h - (metrics.label_h + metrics.input_h + metrics.predict_h + metrics.keys_h +
|
||||
metrics.padding_bottom);
|
||||
metrics.predict_gap = std::max(0.0f, remaining_gap * 0.5f);
|
||||
|
||||
metrics.predict_pos = {config.window_pos.x, config.window_pos.y + metrics.label_h +
|
||||
metrics.input_h + metrics.predict_gap};
|
||||
metrics.predict_size = {metrics.panel_w * (kPredictW / kPanelBaseW), metrics.predict_h};
|
||||
metrics.close_pos = {config.window_pos.x + metrics.panel_w - metrics.close_w,
|
||||
metrics.predict_pos.y};
|
||||
metrics.close_size = {metrics.close_w, metrics.predict_h};
|
||||
|
||||
metrics.kb_pos = {config.window_pos.x + metrics.padding_x,
|
||||
metrics.predict_pos.y + metrics.predict_h + metrics.predict_gap};
|
||||
metrics.kb_size = {metrics.panel_w - metrics.padding_x * 2.0f, metrics.keys_h};
|
||||
|
||||
metrics.key_h =
|
||||
(metrics.kb_size.y - metrics.key_gap * static_cast<float>(kKeyRows - 1)) / kKeyRows;
|
||||
if (metrics.key_h < 8.0f) {
|
||||
metrics.key_h = 8.0f;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
void DrawImeKeyboardGrid(const ImeKbGridLayout& layout, const ImeKbDrawParams& params,
|
||||
ImeKbDrawState& state) {
|
||||
auto* draw = ImGui::GetWindowDrawList();
|
||||
if (!draw || layout.cols <= 0 || layout.rows <= 0 || layout.size.x <= 0.0f ||
|
||||
layout.size.y <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float key_gap_x = layout.key_gap_x;
|
||||
const float key_gap_y = layout.key_gap_y;
|
||||
const float key_h = layout.key_h;
|
||||
const float key_w = (layout.size.x - key_gap_x * (layout.cols - 1)) / layout.cols;
|
||||
|
||||
const auto draw_key = [&](ImVec2 pos, ImVec2 size, ImU32 bg) {
|
||||
draw->AddRectFilled(pos, {pos.x + size.x, pos.y + size.y}, bg, layout.corner_radius);
|
||||
draw->AddRect(pos, {pos.x + size.x, pos.y + size.y}, params.key_border,
|
||||
layout.corner_radius);
|
||||
};
|
||||
const auto draw_key_label = [&](ImVec2 pos, ImVec2 size, ImU32 bg, const char* label) {
|
||||
draw_key(pos, size, bg);
|
||||
if (label && label[0] != '\0') {
|
||||
ImVec2 text_size = ImGui::CalcTextSize(label);
|
||||
ImVec2 text_pos{pos.x + (size.x - text_size.x) * 0.5f,
|
||||
pos.y + (size.y - text_size.y) * 0.5f};
|
||||
draw->AddText(text_pos, params.key_text, label);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr KeySpec blank{1, nullptr, false};
|
||||
const std::array<KeySpec, kKeyCols> row_default = {blank, blank, blank, blank, blank,
|
||||
blank, blank, blank, blank, blank};
|
||||
const std::array<KeySpec, 6> row_space = {
|
||||
blank, blank, blank, {4, nullptr, false}, blank, {2, nullptr, false},
|
||||
};
|
||||
const std::array<KeySpec, 9> row_done = {
|
||||
blank, blank, blank, blank, blank, blank, blank, blank, {2, nullptr, true}};
|
||||
|
||||
auto draw_row = [&](int row_index, const auto& row) {
|
||||
float x = layout.pos.x;
|
||||
float y = layout.pos.y + row_index * (key_h + key_gap_y);
|
||||
for (const auto& key : row) {
|
||||
const float span_w = key_w * key.span + key_gap_x * (key.span - 1);
|
||||
ImVec2 pos{x, y};
|
||||
ImVec2 size{span_w, key_h};
|
||||
ImU32 bg = params.key_bg;
|
||||
if (row_index >= layout.rows - 2) {
|
||||
bg = params.key_bg_alt;
|
||||
}
|
||||
if (key.is_done) {
|
||||
bg = params.key_done;
|
||||
}
|
||||
const char* label = key.is_done ? GetEnterLabel(params.enter_label) : key.label;
|
||||
if (label && label[0] != '\0') {
|
||||
draw_key_label(pos, size, bg, label);
|
||||
} else {
|
||||
draw_key(pos, size, bg);
|
||||
}
|
||||
if (key.is_done) {
|
||||
ImGui::SetCursorScreenPos(pos);
|
||||
ImGui::InvisibleButton("##ImeDialogDoneKey", size);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
state.done_pressed = true;
|
||||
}
|
||||
}
|
||||
x += span_w + key_gap_x;
|
||||
}
|
||||
};
|
||||
|
||||
draw_row(0, row_default);
|
||||
draw_row(1, row_default);
|
||||
draw_row(2, row_default);
|
||||
draw_row(3, row_default);
|
||||
draw_row(4, row_space);
|
||||
draw_row(5, row_done);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ime
|
||||
89
src/core/libraries/ime/ime_kb_layout.h
Normal file
89
src/core/libraries/ime/ime_kb_layout.h
Normal file
@ -0,0 +1,89 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "core/libraries/ime/ime_common.h"
|
||||
|
||||
namespace Libraries::Ime {
|
||||
|
||||
struct ImeViewportMetrics {
|
||||
ImVec2 size{};
|
||||
ImVec2 offset{};
|
||||
float scale_x = 1.0f;
|
||||
float scale_y = 1.0f;
|
||||
float ui_scale = 1.0f;
|
||||
float base_w = 1920.0f;
|
||||
float base_h = 1080.0f;
|
||||
};
|
||||
|
||||
struct ImeKbGridLayout {
|
||||
ImVec2 pos{};
|
||||
ImVec2 size{};
|
||||
float key_gap_x = 0.0f;
|
||||
float key_gap_y = 0.0f;
|
||||
float key_h = 0.0f;
|
||||
int cols = 10;
|
||||
int rows = 6;
|
||||
float corner_radius = 0.0f;
|
||||
};
|
||||
|
||||
struct ImeKbDrawParams {
|
||||
OrbisImeEnterLabel enter_label = OrbisImeEnterLabel::Default;
|
||||
ImU32 key_bg = IM_COL32(35, 35, 35, 255);
|
||||
ImU32 key_bg_alt = IM_COL32(50, 50, 50, 255);
|
||||
ImU32 key_border = IM_COL32(80, 80, 80, 255);
|
||||
ImU32 key_done = IM_COL32(30, 90, 170, 255);
|
||||
ImU32 key_text = IM_COL32(230, 230, 230, 255);
|
||||
};
|
||||
|
||||
struct ImeKbDrawState {
|
||||
bool done_pressed = false;
|
||||
};
|
||||
|
||||
struct ImePanelMetricsConfig {
|
||||
float panel_w = 0.0f;
|
||||
float panel_h = 0.0f;
|
||||
bool multiline = false;
|
||||
bool show_title = true;
|
||||
float base_font_size = 0.0f;
|
||||
ImVec2 window_pos{};
|
||||
};
|
||||
|
||||
struct ImePanelMetrics {
|
||||
float panel_w = 0.0f;
|
||||
float panel_h = 0.0f;
|
||||
float padding_x = 0.0f;
|
||||
float padding_bottom = 0.0f;
|
||||
float label_h = 0.0f;
|
||||
float input_h = 0.0f;
|
||||
float predict_h = 0.0f;
|
||||
float close_w = 0.0f;
|
||||
float keys_h = 0.0f;
|
||||
float key_gap = 0.0f;
|
||||
float key_h = 0.0f;
|
||||
float corner_radius = 0.0f;
|
||||
float label_font_scale = 1.0f;
|
||||
float input_font_scale = 1.0f;
|
||||
float key_font_scale = 1.0f;
|
||||
float predict_gap = 0.0f;
|
||||
ImVec2 input_pos_local{};
|
||||
ImVec2 input_size{};
|
||||
ImVec2 input_pos_screen{};
|
||||
ImVec2 predict_pos{};
|
||||
ImVec2 predict_size{};
|
||||
ImVec2 close_pos{};
|
||||
ImVec2 close_size{};
|
||||
ImVec2 kb_pos{};
|
||||
ImVec2 kb_size{};
|
||||
};
|
||||
|
||||
ImeViewportMetrics ComputeImeViewportMetrics(bool use_over2k);
|
||||
ImePanelMetrics ComputeImePanelMetrics(const ImePanelMetricsConfig& config);
|
||||
|
||||
void DrawImeKeyboardGrid(const ImeKbGridLayout& layout, const ImeKbDrawParams& params,
|
||||
ImeKbDrawState& state);
|
||||
|
||||
} // namespace Libraries::Ime
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "core/libraries/ime/ime_kb_layout.h"
|
||||
#include "ime_ui.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
@ -10,8 +11,6 @@ namespace Libraries::Ime {
|
||||
|
||||
using namespace ImGui;
|
||||
|
||||
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
|
||||
|
||||
ImeState::ImeState(const OrbisImeParam* param, const OrbisImeParamExtended* extended) {
|
||||
if (!param) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid IME parameters");
|
||||
@ -207,26 +206,44 @@ void ImeUi::Draw() {
|
||||
const auto& ctx = *GetCurrentContext();
|
||||
const auto& io = ctx.IO;
|
||||
|
||||
// TODO: Figure out how to properly translate the positions -
|
||||
// for example, if a game wants to center the IME panel,
|
||||
// we have to translate the panel position in a way that it
|
||||
// still becomes centered, as the game normally calculates
|
||||
// the position assuming a it's running on a 1920x1080 screen,
|
||||
// whereas we are running on a 1280x720 window size (by default).
|
||||
//
|
||||
// e.g. Panel position calculation from a game:
|
||||
// param.posx = (1920 / 2) - (panelWidth / 2);
|
||||
// param.posy = (1080 / 2) - (panelHeight / 2);
|
||||
const auto size = GetIO().DisplaySize;
|
||||
f32 pos_x = (ime_param->posx / 1920.0f * (float)size.x);
|
||||
f32 pos_y = (ime_param->posy / 1080.0f * (float)size.y);
|
||||
const bool use_over2k =
|
||||
(ime_param->option & OrbisImeOption::USE_OVER_2K_COORDINATES) != OrbisImeOption::DEFAULT;
|
||||
const auto viewport = Libraries::Ime::ComputeImeViewportMetrics(use_over2k);
|
||||
const float scale_x = viewport.scale_x;
|
||||
const float scale_y = viewport.scale_y;
|
||||
|
||||
ImVec2 window_pos = {pos_x, pos_y};
|
||||
ImVec2 window_size = {500.0f, 100.0f};
|
||||
u32 panel_req_w = 0;
|
||||
u32 panel_req_h = 0;
|
||||
(void)sceImeGetPanelSize(ime_param, &panel_req_w, &panel_req_h);
|
||||
|
||||
// SetNextWindowPos(window_pos);
|
||||
SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f),
|
||||
ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
|
||||
ImVec2 window_size{};
|
||||
if (panel_req_w > 0 && panel_req_h > 0) {
|
||||
window_size = {panel_req_w * scale_x, panel_req_h * scale_y};
|
||||
} else {
|
||||
window_size = {std::min(std::max(0.0f, viewport.size.x - 40.0f), 640.0f),
|
||||
std::min(std::max(0.0f, viewport.size.y - 40.0f), 420.0f)};
|
||||
window_size.x = std::max(window_size.x, 320.0f);
|
||||
window_size.y = std::max(window_size.y, 240.0f);
|
||||
}
|
||||
|
||||
float pos_x = viewport.offset.x + ime_param->posx * scale_x;
|
||||
float pos_y = viewport.offset.y + ime_param->posy * scale_y;
|
||||
if (ime_param->horizontal_alignment == OrbisImeHorizontalAlignment::Center) {
|
||||
pos_x -= window_size.x * 0.5f;
|
||||
} else if (ime_param->horizontal_alignment == OrbisImeHorizontalAlignment::Right) {
|
||||
pos_x -= window_size.x;
|
||||
}
|
||||
if (ime_param->vertical_alignment == OrbisImeVerticalAlignment::Center) {
|
||||
pos_y -= window_size.y * 0.5f;
|
||||
} else if (ime_param->vertical_alignment == OrbisImeVerticalAlignment::Bottom) {
|
||||
pos_y -= window_size.y;
|
||||
}
|
||||
pos_x = std::clamp(pos_x, viewport.offset.x,
|
||||
viewport.offset.x + std::max(0.0f, viewport.size.x - window_size.x));
|
||||
pos_y = std::clamp(pos_y, viewport.offset.y,
|
||||
viewport.offset.y + std::max(0.0f, viewport.size.y - window_size.y));
|
||||
|
||||
SetNextWindowPos({pos_x, pos_y});
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
|
||||
@ -235,40 +252,94 @@ void ImeUi::Draw() {
|
||||
}
|
||||
|
||||
if (Begin("IME##Ime", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings)) {
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||
DrawPrettyBackground();
|
||||
const Libraries::Ime::ImePanelMetricsConfig metrics_cfg{
|
||||
.panel_w = window_size.x,
|
||||
.panel_h = window_size.y,
|
||||
.multiline = True(ime_param->option & OrbisImeOption::MULTILINE),
|
||||
.show_title = false,
|
||||
.base_font_size = GetFontSize(),
|
||||
.window_pos = GetWindowPos(),
|
||||
};
|
||||
const Libraries::Ime::ImePanelMetrics metrics =
|
||||
Libraries::Ime::ComputeImePanelMetrics(metrics_cfg);
|
||||
|
||||
DrawInputText();
|
||||
SetCursorPosY(GetCursorPosY() + 10.0f);
|
||||
SetWindowFontScale(std::max(viewport.ui_scale, metrics.input_font_scale));
|
||||
DrawInputText(metrics);
|
||||
|
||||
const char* button_text;
|
||||
button_text = "Done##ImeDone";
|
||||
auto* draw = GetWindowDrawList();
|
||||
const ImU32 pane_bg = IM_COL32(18, 18, 18, 255);
|
||||
const ImU32 pane_border = IM_COL32(70, 70, 70, 255);
|
||||
draw->AddRectFilled(metrics.predict_pos,
|
||||
{metrics.predict_pos.x + metrics.predict_size.x,
|
||||
metrics.predict_pos.y + metrics.predict_size.y},
|
||||
pane_bg, metrics.corner_radius);
|
||||
draw->AddRect(metrics.predict_pos,
|
||||
{metrics.predict_pos.x + metrics.predict_size.x,
|
||||
metrics.predict_pos.y + metrics.predict_size.y},
|
||||
pane_border, metrics.corner_radius);
|
||||
PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.15f, 1.0f));
|
||||
PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.25f, 0.25f, 1.0f));
|
||||
PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.35f, 0.35f, 0.35f, 1.0f));
|
||||
SetCursorScreenPos(metrics.close_pos);
|
||||
const bool cancel_pressed =
|
||||
Button("X##ImeClose", {metrics.close_size.x, metrics.close_size.y});
|
||||
PopStyleColor(3);
|
||||
|
||||
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;
|
||||
SetCursorScreenPos(metrics.kb_pos);
|
||||
|
||||
SetCursorPosX(button_start_pos);
|
||||
if (!accept_armed) {
|
||||
if (!IsKeyDown(ImGuiKey_Enter) && !IsKeyDown(ImGuiKey_GamepadFaceDown)) {
|
||||
accept_armed = true;
|
||||
}
|
||||
}
|
||||
const bool allow_key_accept = accept_armed;
|
||||
|
||||
if (Button(button_text, BUTTON_SIZE) || (IsKeyPressed(ImGuiKey_Enter))) {
|
||||
state->SendEnterEvent();
|
||||
bool accept_pressed =
|
||||
(allow_key_accept && !metrics_cfg.multiline && IsKeyPressed(ImGuiKey_Enter)) ||
|
||||
(allow_key_accept && IsKeyPressed(ImGuiKey_GamepadFaceDown));
|
||||
|
||||
Libraries::Ime::ImeKbGridLayout kb_layout{};
|
||||
kb_layout.pos = metrics.kb_pos;
|
||||
kb_layout.size = metrics.kb_size;
|
||||
kb_layout.key_gap_x = metrics.key_gap;
|
||||
kb_layout.key_gap_y = metrics.key_gap;
|
||||
kb_layout.key_h = metrics.key_h;
|
||||
kb_layout.cols = 10;
|
||||
kb_layout.rows = 6;
|
||||
kb_layout.corner_radius = metrics.corner_radius;
|
||||
|
||||
Libraries::Ime::ImeKbDrawParams kb_params{};
|
||||
kb_params.enter_label = ime_param->enter_label;
|
||||
kb_params.key_bg_alt = IM_COL32(45, 45, 45, 255);
|
||||
|
||||
Libraries::Ime::ImeKbDrawState kb_state{};
|
||||
SetWindowFontScale(metrics.key_font_scale);
|
||||
Libraries::Ime::DrawImeKeyboardGrid(kb_layout, kb_params, kb_state);
|
||||
SetWindowFontScale(metrics.input_font_scale);
|
||||
if (kb_state.done_pressed) {
|
||||
accept_pressed = true;
|
||||
}
|
||||
|
||||
SameLine(0.0f, button_spacing);
|
||||
Dummy({metrics.kb_size.x, metrics.kb_size.y + metrics.padding_bottom});
|
||||
|
||||
if (Button("Close##ImeClose", BUTTON_SIZE)) {
|
||||
if (accept_pressed) {
|
||||
state->SendEnterEvent();
|
||||
} else if (cancel_pressed) {
|
||||
state->SendCloseEvent();
|
||||
}
|
||||
|
||||
SetWindowFontScale(1.0f);
|
||||
}
|
||||
End();
|
||||
|
||||
first_render = false;
|
||||
}
|
||||
|
||||
void ImeUi::DrawInputText() {
|
||||
ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f};
|
||||
SetCursorPosX(20.0f);
|
||||
void ImeUi::DrawInputText(const ImePanelMetrics& metrics) {
|
||||
const ImVec2 input_size = metrics.input_size;
|
||||
SetCursorPos(metrics.input_pos_local);
|
||||
if (first_render) {
|
||||
SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ namespace Libraries::Ime {
|
||||
|
||||
class ImeHandler;
|
||||
class ImeUi;
|
||||
struct ImePanelMetrics;
|
||||
|
||||
class ImeState {
|
||||
friend class ImeHandler;
|
||||
@ -57,6 +58,7 @@ class ImeUi : public ImGui::Layer {
|
||||
const OrbisImeParamExtended* extended_param{};
|
||||
|
||||
bool first_render = true;
|
||||
bool accept_armed = false;
|
||||
std::mutex draw_mutex;
|
||||
|
||||
public:
|
||||
@ -71,9 +73,9 @@ public:
|
||||
private:
|
||||
void Free();
|
||||
|
||||
void DrawInputText();
|
||||
void DrawInputText(const ImePanelMetrics& metrics);
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData* data);
|
||||
};
|
||||
|
||||
}; // namespace Libraries::Ime
|
||||
}; // namespace Libraries::Ime
|
||||
|
||||
@ -47,6 +47,8 @@ struct SwVersionStruct {
|
||||
u32 hex_representation;
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret);
|
||||
|
||||
struct AuthInfoData {
|
||||
u64 paid;
|
||||
u64 caps[4];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user