mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +02:00
260 lines
7.5 KiB
C++
260 lines
7.5 KiB
C++
#include "libs/imeCommon.h"
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
namespace Libs::ImeCommon {
|
|
namespace {
|
|
|
|
constexpr bool IsHighSurrogate(char16_t value) {
|
|
return value >= 0xd800 && value <= 0xdbff;
|
|
}
|
|
|
|
constexpr bool IsLowSurrogate(char16_t value) {
|
|
return value >= 0xdc00 && value <= 0xdfff;
|
|
}
|
|
|
|
char16_t HidCharacter(uint16_t keycode, uint32_t status) {
|
|
const bool shift = (status & 0x00002200) != 0;
|
|
const bool caps = (status & 0x00020000) != 0;
|
|
if (keycode >= 4 && keycode <= 29) {
|
|
return static_cast<char16_t>(((shift != caps) ? u'A' : u'a') + keycode - 4);
|
|
}
|
|
if (keycode >= 30 && keycode <= 39) {
|
|
static constexpr char16_t plain[] = u"1234567890";
|
|
static constexpr char16_t shifted[] = u"!@#$%^&*()";
|
|
return (shift ? shifted : plain)[keycode - 30];
|
|
}
|
|
if (keycode == 44) {
|
|
return u' ';
|
|
}
|
|
if (keycode >= 45 && keycode <= 56) {
|
|
static constexpr char16_t plain[] = u"-=[]\\#;'`,./";
|
|
static constexpr char16_t shifted[] = u"_+{}|~:\"~<>?";
|
|
return (shift ? shifted : plain)[keycode - 45];
|
|
}
|
|
if (keycode >= 89 && keycode <= 98) {
|
|
static constexpr char16_t keypad[] = u"1234567890";
|
|
return keypad[keycode - 89];
|
|
}
|
|
return keycode == 99 ? u'.' : u'\0';
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool IsValidUtf16(std::u16string_view text) {
|
|
for (size_t i = 0; i < text.size(); i++) {
|
|
if (IsHighSurrogate(text[i])) {
|
|
if (++i >= text.size() || !IsLowSurrogate(text[i])) {
|
|
return false;
|
|
}
|
|
} else if (IsLowSurrogate(text[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IsValidInputText(std::u16string_view text, bool multiline) {
|
|
if (!IsValidUtf16(text)) {
|
|
return false;
|
|
}
|
|
return std::none_of(text.begin(), text.end(), [multiline](char16_t value) {
|
|
return value == u'\0' || (!multiline && (value == u'\n' || value == u'\r'));
|
|
});
|
|
}
|
|
|
|
bool IsAllowedInput(char16_t value, Type type, uint32_t option) {
|
|
if (value == u'\r' || value == u'\0') {
|
|
return false;
|
|
}
|
|
if (value == u'\n') {
|
|
return (option & OPTION_MULTILINE) != 0;
|
|
}
|
|
if (type == Type::Number) {
|
|
return (value >= u'0' && value <= u'9') || value == u',' || value == u'-' || value == u'.';
|
|
}
|
|
if (type == Type::BasicLatin) {
|
|
return value >= u' ' && value <= u'~';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint32_t NormalizeCursor(std::u16string_view text, uint32_t cursor) {
|
|
cursor = std::min<uint32_t>(cursor, static_cast<uint32_t>(text.size()));
|
|
if (cursor > 0 && cursor < text.size() && IsHighSurrogate(text[cursor - 1]) &&
|
|
IsLowSurrogate(text[cursor])) {
|
|
cursor++;
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
void ClampText(std::u16string* text, uint32_t limit) {
|
|
if (text->size() <= limit) {
|
|
return;
|
|
}
|
|
text->resize(limit);
|
|
if (!text->empty() && IsHighSurrogate(text->back())) {
|
|
text->pop_back();
|
|
}
|
|
}
|
|
|
|
EditDelta ComputeEditDelta(std::u16string_view before, std::u16string_view after) {
|
|
size_t prefix = 0;
|
|
while (prefix < before.size() && prefix < after.size() && before[prefix] == after[prefix]) {
|
|
prefix++;
|
|
}
|
|
size_t before_tail = before.size();
|
|
size_t after_tail = after.size();
|
|
while (before_tail > prefix && after_tail > prefix &&
|
|
before[before_tail - 1] == after[after_tail - 1]) {
|
|
before_tail--;
|
|
after_tail--;
|
|
}
|
|
return {static_cast<uint32_t>(prefix),
|
|
static_cast<int32_t>(after_tail - prefix) - static_cast<int32_t>(before_tail - prefix)};
|
|
}
|
|
|
|
bool RunTextFilter(TextFilter filter, const std::u16string& source, uint32_t output_capacity,
|
|
std::u16string* output) {
|
|
if (filter == nullptr || output == nullptr) {
|
|
return false;
|
|
}
|
|
std::vector<char16_t> buffer(static_cast<size_t>(output_capacity) + 1, u'\0');
|
|
uint32_t output_length = output_capacity;
|
|
if (filter(buffer.data(), &output_length, source.c_str(),
|
|
static_cast<uint32_t>(source.size())) != 0 ||
|
|
output_length > output_capacity ||
|
|
!IsValidUtf16(std::u16string_view(buffer.data(), output_length))) {
|
|
return false;
|
|
}
|
|
output->assign(buffer.data(), buffer.data() + output_length);
|
|
return true;
|
|
}
|
|
|
|
bool ApplyKeyboardFilterOutput(ExternalInput* input, uint16_t keycode, uint32_t status,
|
|
bool multiline) {
|
|
constexpr uint32_t KEYCODE_VALID = 0x00000001;
|
|
constexpr uint32_t CHARACTER_VALID = 0x00000002;
|
|
if (keycode == input->key.keycode && status == input->key.status) {
|
|
return true;
|
|
}
|
|
if ((status & KEYCODE_VALID) == 0 || keycode == 0) {
|
|
return input->action == ExternalAction::Text && (status & CHARACTER_VALID) != 0;
|
|
}
|
|
input->key.keycode = keycode;
|
|
input->key.status = status;
|
|
input->text.clear();
|
|
switch (keycode) {
|
|
case 40:
|
|
case 88:
|
|
case 158:
|
|
input->action = multiline ? ExternalAction::Newline : ExternalAction::Accept;
|
|
break;
|
|
case 41: input->action = ExternalAction::Cancel; break;
|
|
case 42:
|
|
case 187: input->action = ExternalAction::Backspace; break;
|
|
case 43:
|
|
case 186: input->action = ExternalAction::None; break;
|
|
case 79: input->action = ExternalAction::MoveRight; break;
|
|
case 80: input->action = ExternalAction::MoveLeft; break;
|
|
default: {
|
|
const char16_t character = HidCharacter(keycode, status);
|
|
if (character == u'\0') {
|
|
return false;
|
|
}
|
|
input->action = ExternalAction::Text;
|
|
input->key.character = character;
|
|
input->text.push_back(character);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TextEditEngine::Reset(Type type, uint32_t option, uint32_t max_length, std::u16string text) {
|
|
m_type = type;
|
|
m_option = option;
|
|
m_max_length = max_length;
|
|
ClampText(&text, max_length);
|
|
m_text = std::move(text);
|
|
m_cursor = static_cast<uint32_t>(m_text.size());
|
|
}
|
|
|
|
bool TextEditEngine::Insert(std::u16string_view text, EditDelta* edit) {
|
|
if (text.empty() || !IsValidUtf16(text)) {
|
|
return false;
|
|
}
|
|
std::u16string allowed;
|
|
allowed.reserve(text.size());
|
|
for (const char16_t value: text) {
|
|
if (IsAllowedInput(value, m_type, m_option)) {
|
|
allowed.push_back(value);
|
|
}
|
|
}
|
|
const size_t available = m_max_length - m_text.size();
|
|
if (allowed.empty() || available == 0) {
|
|
return false;
|
|
}
|
|
allowed.resize(std::min(allowed.size(), available));
|
|
if (!allowed.empty() && IsHighSurrogate(allowed.back())) {
|
|
allowed.pop_back();
|
|
}
|
|
if (allowed.empty()) {
|
|
return false;
|
|
}
|
|
if (edit != nullptr) {
|
|
*edit = {m_cursor, static_cast<int32_t>(allowed.size())};
|
|
}
|
|
m_text.insert(m_cursor, allowed);
|
|
m_cursor += static_cast<uint32_t>(allowed.size());
|
|
return true;
|
|
}
|
|
|
|
bool TextEditEngine::Backspace(EditDelta* edit) {
|
|
if (m_cursor == 0) {
|
|
return false;
|
|
}
|
|
uint32_t first = m_cursor - 1;
|
|
if (first > 0 && IsLowSurrogate(m_text[first]) && IsHighSurrogate(m_text[first - 1])) {
|
|
first--;
|
|
}
|
|
if (edit != nullptr) {
|
|
*edit = {first, -static_cast<int32_t>(m_cursor - first)};
|
|
}
|
|
m_text.erase(first, m_cursor - first);
|
|
m_cursor = first;
|
|
return true;
|
|
}
|
|
|
|
bool TextEditEngine::MoveCursor(int delta) {
|
|
if (delta == 0) {
|
|
return false;
|
|
}
|
|
int next = std::clamp(static_cast<int>(m_cursor) + delta, 0, static_cast<int>(m_text.size()));
|
|
if (delta < 0 && next > 0 && next < static_cast<int>(m_text.size()) &&
|
|
IsLowSurrogate(m_text[next]) && IsHighSurrogate(m_text[next - 1])) {
|
|
next--;
|
|
} else if (delta > 0 && next > 0 && next < static_cast<int>(m_text.size()) &&
|
|
IsHighSurrogate(m_text[next - 1]) && IsLowSurrogate(m_text[next])) {
|
|
next++;
|
|
}
|
|
if (next == static_cast<int>(m_cursor)) {
|
|
return false;
|
|
}
|
|
m_cursor = static_cast<uint32_t>(next);
|
|
return true;
|
|
}
|
|
|
|
void TextEditEngine::ReplaceText(std::u16string text, uint32_t cursor) {
|
|
ClampText(&text, m_max_length);
|
|
m_text = std::move(text);
|
|
m_cursor = NormalizeCursor(m_text, cursor);
|
|
}
|
|
|
|
void TextEditEngine::SetCursor(uint32_t cursor) {
|
|
m_cursor = NormalizeCursor(m_text, cursor);
|
|
}
|
|
|
|
} // namespace Libs::ImeCommon
|