diff --git a/Common/Data/Encoding/Utf8.cpp b/Common/Data/Encoding/Utf8.cpp index 09abf6090d..4e7a2c7a63 100644 --- a/Common/Data/Encoding/Utf8.cpp +++ b/Common/Data/Encoding/Utf8.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "Common/Data/Encoding/Utf8.h" #include "Common/Data/Encoding/Utf16.h" @@ -373,6 +374,46 @@ void ConvertUTF8ToJavaModifiedUTF8(std::string *output, std::string_view input) _dbg_assert_(output->size() >= input.size()); } +std::string NormalizeForSearch(std::string_view input) { + std::string result; + // Pre-allocating input size is a good heuristic, though the + // result could be slightly smaller after normalization. + result.reserve(input.size()); + + int index = 0; + int size = static_cast(input.size()); + char buffer[4]; // Temporary buffer for UTF-8 encoding + + while (index < size) { + uint32_t codepoint = u8_nextchar(input.data(), &index, size); + + // 1. Convert Fullwidth Roman/Numbers to ASCII + // Range: U+FF01 (!) to U+FF5E (~) + if (codepoint >= 0xFF01 && codepoint <= 0xFF5E) { + codepoint -= 0xFEE0; + } + // Convert Fullwidth Space (U+3000) to standard space + else if (codepoint == 0x3000) { + codepoint = 0x20; + } + + // 2. Lowercase (Basic Latin range) + // We do this after the wide-to-ascii conversion to catch characters + // that were originally wide uppercase (e.g., 'A' -> 'A' -> 'a'). + if (codepoint >= 'A' && codepoint <= 'Z') { + codepoint += ('a' - 'A'); + } + + // 3. Re-encode back to UTF-8 + int bytes_written = u8_wc_toutf8(buffer, codepoint); + if (bytes_written > 0) { + result.append(buffer, bytes_written); + } + } + + return result; +} + #ifndef _WIN32 // Replacements for the Win32 wstring functions. Not to be used from emulation code! diff --git a/Common/Data/Encoding/Utf8.h b/Common/Data/Encoding/Utf8.h index ba72eeb9fe..9a752f8c70 100644 --- a/Common/Data/Encoding/Utf8.h +++ b/Common/Data/Encoding/Utf8.h @@ -129,3 +129,6 @@ std::u16string ConvertUTF8ToUCS2(std::string_view source); // Java needs 4-byte UTF-8 to be converted to surrogate pairs, each component of which get // encoded into 3-byte UTF-8. void ConvertUTF8ToJavaModifiedUTF8(std::string *output, std::string_view input); + +// Applies "tolower" and also rewrites full-with japanese characters. +std::string NormalizeForSearch(std::string_view input); diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index 96e2c3fee5..6e3841f77f 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -563,6 +563,8 @@ bool GameBrowser::Key(const KeyInput &input) { // TODO: Restore focus state here. UI::EnableFocusMovement(false); } + } else { + // Empty search filter. Navigate upwards on backspace? } } else if (!searchFilter_.empty() && input.keyCode == NKCODE_ESCAPE) { searchFilter_.clear(); @@ -594,8 +596,7 @@ void GameBrowser::ApplySearchFilter(bool setKeyboardFocus) { return; } - std::string filter = searchFilter_; - std::transform(filter.begin(), filter.end(), filter.begin(), tolower); + std::string filter = NormalizeForSearch(searchFilter_); searchPending_ = false; // By default, everything is matching. @@ -632,7 +633,7 @@ void GameBrowser::ApplySearchFilter(bool setKeyboardFocus) { continue; } - std::transform(label.begin(), label.end(), label.begin(), tolower); + label = NormalizeForSearch(label); bool match = v->CanBeFocused() && label.find(filter) != label.npos; if (match && !firstMatch) { firstMatch = v; @@ -1152,6 +1153,9 @@ void GameBrowser::NavigateClick(UI::EventParams &e) { path_.Navigate(text.ToString()); } g_Config.currentDirectory = path_.GetPath(); + // Clear the search filter. This allow for smooth directory navigation using search + // (although there's no good way of going up...) + SetSearchFilter("", false); Refresh(); }