UI implementation

This commit is contained in:
Lander Gallastegi 2024-10-04 18:16:04 +02:00
parent ca855e3295
commit b95576f6e3
3 changed files with 384 additions and 67 deletions

7
src/core/libraries/dialogs/ime_dialog.h Normal file → Executable file
View file

@ -3,6 +3,7 @@
#pragma once
#include "common/enum.h"
#include "common/types.h"
namespace Core::Loader {
@ -89,6 +90,8 @@ enum class OrbisImeDialogOption : u32 {
NO_AUTO_COMPLETION = 4
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption)
enum class OrbisImeInputMethod : u32 {
DEFAULT = 0
};
@ -167,7 +170,7 @@ struct OrbisImeDialogResult {
struct OrbisImeKeycode {
u16 keycode;
wchar_t character;
char16_t character;
u32 status;
OrbisImeKeyboardType type;
s32 userId;
@ -175,7 +178,7 @@ struct OrbisImeKeycode {
u64 timestamp;
};
typedef int (*OrbisImeTextFilter)(wchar_t* outText, u32* outTextLength, const wchar_t* srcText,
typedef int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength, const char16_t* srcText,
u32 srcTextLength);
typedef int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, u16* outKeycode,

390
src/core/libraries/dialogs/ime_dialog_ui.cpp Normal file → Executable file
View file

@ -7,45 +7,51 @@
#include <cwchar>
#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"
#ifndef _WIN32
#define IME_NATIVE_ENCODING "WCHAR_T"
#define IME_UTF8_ENCODING "UTF-8"
#define IME_ORBIS_ENCODING "UTF-16LE"
#else
#include <Windows.h>
#endif
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;
isMultiLine = param->option & OrbisImeDialogOption::MULTILINE;
is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE);
type = param->type;
enterLabel = param->enterLabel;
textFilter = param->filter;
extKeyboardFilter = extended ? extended->extKeyboardFilter : nullptr;
maxTextLength = param->maxTextLength;
textBuffer = param->inputTextBuffer;
enter_label = param->enterLabel;
text_filter = param->filter;
keyboard_filter = extended ? extended->extKeyboardFilter : nullptr;
max_text_length = param->maxTextLength;
text_buffer = param->inputTextBuffer;
#ifndef _WIN32
orbis_to_native = iconv_open(IME_NATIVE_ENCODING, IME_ORBIS_ENCODING);
native_to_orbis = iconv_open(IME_ORBIS_ENCODING , IME_NATIVE_ENCODING);
orbis_to_utf8 = iconv_open(IME_UTF8_ENCODING, IME_ORBIS_ENCODING);
utf8_to_orbis = iconv_open(IME_ORBIS_ENCODING , IME_UTF8_ENCODING);
ASSERT_MSG(orbis_to_native != (iconv_t)-1, "Failed to open iconv orbis_to_native");
ASSERT_MSG(native_to_orbis != (iconv_t)-1, "Failed to open iconv native_to_orbis");
std::size_t title_len = std::char_traits<char16_t>::length(param->title) + 1;
#elif
std::size_t title_len = std::wcslen(reinterpret_cast<const wchar_t*>(param->title)) + 1;
ASSERT_MSG(orbis_to_utf8 != (iconv_t)-1, "Failed to open iconv orbis_to_utf8");
ASSERT_MSG(utf8_to_orbis != (iconv_t)-1, "Failed to open iconv utf8_to_orbis");
#endif
title = new wchar_t[title_len];
if (!ConvertOrbisToNative(param->title, title_len, title, title_len)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert title to native encoding");
std::size_t title_len = std::char_traits<char16_t>::length(param->title);
title = new char[title_len * 4 + 1];
if (!ConvertOrbisToUTF8(param->title, title_len, title, title_len)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding");
return;
}
@ -53,26 +59,23 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, const OrbisImeP
return;
}
#ifndef _WIN32
std::size_t placeholder_len = std::char_traits<char16_t>::length(param->placeholder) + 1;
#elif
std::size_t placeholder_len = std::wcslen(reinterpret_cast<const wchar_t*>(param->placeholder)) + 1;
#endif
placeholder = new wchar_t[placeholder_len];
std::size_t placeholder_len = std::char_traits<char16_t>::length(param->placeholder);
placeholder = new char[placeholder_len * 4 + 1];
if (!ConvertOrbisToNative(param->placeholder, placeholder_len, placeholder, placeholder_len)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to native encoding");
if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, placeholder, placeholder_len)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding");
}
}
ImeDialogState::~ImeDialogState() {
#ifndef _WIN32
if (orbis_to_native != (iconv_t)-1) {
iconv_close(orbis_to_native);
if (orbis_to_utf8 != (iconv_t)-1) {
iconv_close(orbis_to_utf8);
}
if (native_to_orbis != (iconv_t)-1) {
iconv_close(native_to_orbis);
if (utf8_to_orbis != (iconv_t)-1) {
iconv_close(utf8_to_orbis);
}
#endif
if (title) {
delete[] title;
@ -81,50 +84,343 @@ ImeDialogState::~ImeDialogState() {
if (placeholder) {
delete[] placeholder;
}
#endif
}
bool ImeDialogState::CallTextFilter() {
if (!textFilter) {
if (!text_filter || !input_changed) {
return true;
}
//TODO
input_changed = false;
char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0};
u32 src_text_length = std::strlen(current_text);
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, 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, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
return false;
}
return true;
}
bool ImeDialogState::ConvertOrbisToNative(const char16_t* orbis_text, std::size_t orbis_text_len, wchar_t* native_text, std::size_t native_text_len) {
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) {
#ifndef _WIN32
std::size_t orbis_text_len_bytes = orbis_text_len * sizeof(char16_t);
std::size_t native_text_len_bytes = native_text_len * sizeof(wchar_t);
std::size_t utf8_text_len_bytes = utf8_text_len * sizeof(char);
char16_t* orbis_text_ptr = const_cast<char16_t*>(orbis_text);
wchar_t* native_text_ptr = native_text;
char* utf8_text_ptr = utf8_text;
std::size_t result = iconv(orbis_to_native, (char**)&orbis_text_ptr, &orbis_text_len_bytes, (char**)&native_text_ptr, &native_text_len_bytes);
std::size_t result = iconv(orbis_to_utf8, (char**)&orbis_text_ptr, &orbis_text_len_bytes, (char**)&utf8_text_ptr, &utf8_text_len_bytes);
if (result == (std::size_t)-1) {
return false;
}
*utf8_text_ptr = '\0'; // Null-terminate the string
return true;
#else
int required_size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<const wchar_t*>(orbis_text), orbis_text_len, nullptr, 0, nullptr, nullptr);
if (required_size > utf8_text_len) {
return false;
}
int converted_size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<const wchar_t*>(orbis_text), orbis_text_len, utf8_text, utf8_text_len, nullptr, nullptr);
if (required_size == 0) {
return false;
}
utf8_text[converted_size] = '\0';
return result != -1;
#elif
std::wcsncpy(native_text, reinterpret_cast<const wchar_t*>(orbis_text), native_text_len);
return true;
#endif
}
bool ImeDialogState::ConvertNativeToOrbis(const wchar_t* native_text, std::size_t native_text_len, char16_t* orbis_text, std::size_t orbis_text_len) {
bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, char16_t* orbis_text, std::size_t orbis_text_len) {
#ifndef _WIN32
std::size_t native_text_len_bytes = native_text_len * sizeof(wchar_t);
std::size_t utf8_text_len_bytes = utf8_text_len * sizeof(char);
std::size_t orbis_text_len_bytes = orbis_text_len * sizeof(char16_t);
wchar_t* native_text_ptr = const_cast<wchar_t*>(native_text);
char* utf8_text_ptr = const_cast<char*>(utf8_text);
char16_t* orbis_text_ptr = orbis_text;
std::size_t result = iconv(native_to_orbis, (char**)&native_text_ptr, &native_text_len_bytes, (char**)&orbis_text_ptr, &orbis_text_len_bytes);
std::size_t result = iconv(utf8_to_orbis, (char**)&utf8_text_ptr, &utf8_text_len_bytes, (char**)&orbis_text_ptr, &orbis_text_len_bytes);
if (result == (std::size_t)-1) {
return false;
}
*orbis_text_ptr = u'\0'; // Null-terminate the string
return true;
#else
int required_size = MultiByteToWideChar(CP_UTF8, 0, utf8_text, utf8_text_len, nullptr, 0);
if (required_size > orbis_text_len) {
return false;
}
int converted_size = MultiByteToWideChar(CP_UTF8, 0, utf8_text, utf8_text_len, reinterpret_cast<wchar_t*>(orbis_text), orbis_text_len);
if (required_size == 0) {
return false;
}
orbis_text[converted_size] = u'\0';
return result != -1;
#elif
std::wcsncpy(reinterpret_cast<wchar_t*>(orbis_text), native_text, orbis_text_len);
return true;
#endif
}
bool ImeDialogState::ConvertOrbisCharToUTF8(const char16_t orbis_char, char* utf8_char, std::size_t& utf8_char_len) {
std::fill(utf8_char, utf8_char + 4, '\0');
#ifndef _WIN32
std::size_t orbis_char_len_bytes = sizeof(char16_t);
std::size_t utf8_char_len_bytes = utf8_char_len;
char16_t orbis_char_ptr = orbis_char;
char* utf8_char_ptr = utf8_char;
std::size_t result = iconv(orbis_to_utf8, (char**)&orbis_char_ptr, &orbis_char_len_bytes, (char**)&utf8_char_ptr, &utf8_char_len_bytes);
if (result == (std::size_t)-1) {
utf8_char_len = 0;
return false;
}
utf8_char_len = 4 - utf8_char_len_bytes;
return true;
#else
int required_size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<const wchar_t*>(&orbis_char), 1, nullptr, 0, nullptr, nullptr);
if (required_size > 4) {
UNREACHABLE_MSG("UTF-8 character is never more than 4 bytes");
}
*utf8_char_len = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast<const wchar_t*>(&orbis_char), 1, utf8_char, 4, nullptr, nullptr);
return *utf8_char_len != 0;
#endif
}
bool ImeDialogState::ConvertUTF8CharToOrbis(const char* utf8_char, char16_t& orbis_char) {
#ifndef _WIN32
std::size_t utf8_char_len_bytes = 4 * sizeof(char);
std::size_t orbis_char_len_bytes = sizeof(char16_t);
char* utf8_char_ptr = const_cast<char*>(utf8_char);
char16_t* orbis_char_ptr = &orbis_char;
std::size_t result = iconv(utf8_to_orbis, (char**)&utf8_char_ptr, &utf8_char_len_bytes, (char**)&orbis_char_ptr, &orbis_char_len_bytes);
if (result == (std::size_t)-1) {
return false;
}
return true;
#else
int required_size = MultiByteToWideChar(CP_UTF8, 0, utf8_char, std::strlen(utf8_char), reinterpret_cast<wchar_t*>(&orbis_char), 1);
return required_size != 0;
#endif
}
ImeDialogUi::ImeDialogUi(ImeDialogState* state, OrbisImeDialogStatus* status, OrbisImeDialogResult* result)
: state(state), status(status), result(result) {
if (state && *status == OrbisImeDialogStatus::RUNNING) {
AddLayer(this);
}
}
ImeDialogUi::~ImeDialogUi() {
RemoveLayer(this);
}
ImeDialogUi::ImeDialogUi(ImeDialogUi&& other) noexcept
: state(other.state), status(other.status), result(other.result) {
if (state) std::scoped_lock lock(state->mutex);
if (other.state) std::scoped_lock lock2(other.state->mutex);
other.state = nullptr;
other.status = nullptr;
other.result = nullptr;
if (state && *status == OrbisImeDialogStatus::RUNNING) {
AddLayer(this);
}
}
ImeDialogUi& ImeDialogUi::operator=(ImeDialogUi other) {
if (state) std::scoped_lock lock(state->mutex);
if (other.state) std::scoped_lock lock2(other.state->mutex);
std::swap(state, other.state);
std::swap(status, other.status);
std::swap(result, other.result);
if (state) {
AddLayer(this);
}
return *this;
}
void ImeDialogUi::Draw() {
if (!state) {
return;
}
std::unique_lock lock{state->mutex};
if (!status || *status != OrbisImeDialogStatus::RUNNING) {
return;
}
std::scoped_lock lock2{state->mutex};
const auto& ctx = *GetCurrentContext();
const auto& io = ctx.IO;
ImVec2 window_size;
if (state->is_multiLine) {
window_size = {400.0f, 200.0f};
} else {
window_size = {400.0f, 100.0f};
}
CentralizeWindow();
SetNextWindowSize(window_size);
SetNextWindowCollapsed(false);
if (first_render || !io.NavActive) {
SetNextWindowFocus();
}
first_render = false;
if (Begin("IME Dialog#ImeDialog", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
DrawPrettyBackground();
Separator();
if (state->title) {
SetWindowFontScale(1.7f);
TextUnformatted(state->title);
SetWindowFontScale(1.0f);
Separator();
}
if (state->is_multiLine) {
DrawMultiLineInputText();
} else {
DrawInputText();
}
Separator();
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;
}
if (Button(button_text, BUTTON_SIZE)) {
*status = OrbisImeDialogStatus::FINISHED;
result->endstatus = OrbisImeDialogEndStatus::OK;
}
if (Button("Cancel#ImeDialogCancel", BUTTON_SIZE)) {
*status = OrbisImeDialogStatus::FINISHED;
result->endstatus = OrbisImeDialogEndStatus::USER_CANCELED;
}
}
End();
}
void ImeDialogUi::DrawInputText() {
if (InputTextEx("##ImeDialogInput", state->placeholder, state->current_text, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH, ImVec2(0, 0), ImGuiInputTextFlags_CallbackCharFilter, InputTextCallback, this)) {
state->input_changed = true;
}
}
void ImeDialogUi::DrawMultiLineInputText() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"
if (InputTextEx("##ImeDialogInput", state->placeholder, state->current_text, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH, ImVec2(380.0f, 100.0f), ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_Multiline, InputTextCallback, this)) {
state->input_changed = true;
}
#pragma clang diagnostic pop
}
int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
ImeDialogUi* ui = static_cast<ImeDialogUi*>(data->UserData);
ASSERT(ui);
// 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->ConvertUTF8CharToOrbis(event_char, src_keycode.character)) {
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

54
src/core/libraries/dialogs/ime_dialog_ui.h Normal file → Executable file
View file

@ -3,6 +3,7 @@
#pragma once
#include <mutex>
#ifndef _WIN32
#include <iconv.h>
#endif
@ -18,22 +19,26 @@ class ImeDialogUi;
class ImeDialogState final {
friend ImeDialogUi;
OrbisImeDialogStatus status = OrbisImeDialogStatus::NONE;
bool input_changed = false;
s32 userId{};
bool isMultiLine{};
bool is_multiLine{};
OrbisImeType type{};
OrbisImeEnterLabel enterLabel{};
OrbisImeTextFilter textFilter{};
OrbisImeExtKeyboardFilter extKeyboardFilter{};
u32 maxTextLength{};
char16_t* textBuffer{};
wchar_t* title = nullptr;
wchar_t* placeholder = nullptr;
wchar_t currentText[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH] = {0};
OrbisImeEnterLabel enter_label{};
OrbisImeTextFilter text_filter{};
OrbisImeExtKeyboardFilter keyboard_filter{};
u32 max_text_length{};
char16_t* text_buffer{};
char* title = nullptr;
char* placeholder = nullptr;
// A character can hold up to 4 bytes in UTF-8
char current_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4] = {0};
std::mutex mutex;
#ifndef _WIN32
iconv_t orbis_to_native = (iconv_t)-1;
iconv_t native_to_orbis = (iconv_t)-1;
iconv_t orbis_to_utf8 = (iconv_t)-1;
iconv_t utf8_to_orbis = (iconv_t)-1;
#endif
public:
ImeDialogState(const OrbisImeDialogParam* param = nullptr, const OrbisImeParamExtended* extended = nullptr);
@ -42,23 +47,36 @@ public:
ImeDialogState() = default;
bool CallTextFilter();
private:
bool ConvertOrbisToNative(const char16_t* orbis_text, std::size_t orbis_text_len, wchar_t* native_text, std::size_t native_text_len);
bool ConvertNativeToOrbis(const wchar_t* native_text, std::size_t native_text_len, char16_t* orbis_text, std::size_t orbis_text_len);
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);
bool ConvertOrbisCharToUTF8(const char16_t orbis_char, char* utf8_char, std::size_t& utf8_char_len);
bool ConvertUTF8CharToOrbis(const char* utf8_char, char16_t& orbis_char);
};
class ImeDialogUi final : public ImGui::Layer {
public:
explicit ImeDialogUi();
ImeDialogState* state{};
OrbisImeDialogStatus* status{};
OrbisImeDialogResult* result{};
bool first_render = true;
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 DrawInputText();
void DrawMultiLineInputText();
static int InputTextCallback(ImGuiInputTextCallbackData* data);
};
} // namespace Libraries::ImeDialog