From 2d900584214e38d804130eeb057094e1fc1f35d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 3 May 2026 17:44:01 +0200 Subject: [PATCH] Move more of the search code out of GameBrowser --- UI/GameBrowser.cpp | 110 -------------------------------------------- UI/GameBrowser.h | 18 +------- UI/MiscViews.cpp | 112 +++++++++++++++++++++++++++++++++++++++++++++ UI/MiscViews.h | 16 +++++++ 4 files changed, 129 insertions(+), 127 deletions(-) diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index 014be12419..ab2ae86104 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -558,122 +558,12 @@ bool GameBrowser::Key(const KeyInput &input) { return search_.Key(gameList_, input); } -bool SearchEngine::Key(UI::ViewGroup *viewGroup, const KeyInput &input) { - bool retval = false; - // Only one is visible at a time, so we can just grab all Char input. - if (input.flags & KeyInputFlags::CHAR) { - const int unichar = input.keyCode; - if (unichar >= 0x20) { - // TODO: Save focus state here. - - // Insert it! (todo: do it with a string insert) - char buf[8]; - buf[u8_wc_toutf8(buf, unichar)] = '\0'; - searchFilter += buf; - ApplySearchFilter(viewGroup, true); - retval = true; - } - } else if (input.flags & KeyInputFlags::DOWN) { - if (input.keyCode == NKCODE_DEL) { - if (!searchFilter.empty()) { - searchFilter.pop_back(); - ApplySearchFilter(viewGroup, true); - retval = true; - if (searchFilter.empty()) { - // 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(); - ApplySearchFilter(viewGroup, false); - retval = true; - - // TODO: Restore focus state here. - UI::EnableFocusMovement(false); - } - } - return retval; -} - void GameBrowser::SetSearchFilter(const std::string &filter, bool setKeyboardFocus) { search_.searchFilter = filter; // We don't refresh because game info loads asynchronously anyway. search_.ApplySearchFilter(gameList_, setKeyboardFocus); } -void SearchEngine::ApplySearchFilter(UI::ViewGroup *viewGroup, bool setKeyboardFocus) { - if (searchBar) { - searchBar->SetSearchFilter(searchFilter); - searchBar->SetVisibility(searchFilter.empty() ? UI::V_GONE : UI::V_VISIBLE); - } - - if (searchFilter.empty() && searchStates.empty()) { - // We haven't hidden anything, and we're not searching, so do nothing. - searchPending = false; - return; - } - - std::string filter = NormalizeForSearch(searchFilter); - - searchPending = false; - // By default, everything is matching. - searchStates.resize(viewGroup->GetNumSubviews(), SearchState::MATCH); - if (filter.empty()) { - // Just quickly mark anything we hid as visible again. - for (int i = 0; i < viewGroup->GetNumSubviews(); ++i) { - UI::View *v = viewGroup->GetViewByIndex(i); - if (searchStates[i] != SearchState::MATCH) - v->SetVisibility(UI::V_VISIBLE); - } - - searchStates.clear(); - return; - } - - UI::View *firstMatch = nullptr; - - for (int i = 0; i < viewGroup->GetNumSubviews(); ++i) { - UI::View *v = viewGroup->GetViewByIndex(i); - std::string label = v->DescribeText(); - // This is a bit of a hack to recognize a pending game title. - if (label == "...") { - searchPending = true; - // Hide anything pending while, we'll pop-in search results as they match. - // Note: we leave it at MATCH if gone before, so we don't show it again. - if (v->GetVisibility() == UI::V_VISIBLE) { - if (searchStates[i] == SearchState::MATCH) - v->SetVisibility(UI::V_GONE); - searchStates[i] = SearchState::PENDING; - } - continue; - } - - label = NormalizeForSearch(label); - bool match = v->CanBeFocused() && label.find(filter) != label.npos; - if (match && !firstMatch) { - firstMatch = v; - } - if (match && searchStates[i] != SearchState::MATCH) { - // It was previously visible and force hidden, so show it again. - v->SetVisibility(UI::V_VISIBLE); - searchStates[i] = SearchState::MATCH; - } else if (!match && searchStates[i] == SearchState::MATCH && v->GetVisibility() == UI::V_VISIBLE) { - v->SetVisibility(UI::V_GONE); - searchStates[i] = SearchState::MISMATCH; - } - } - - if (firstMatch) { - if (setKeyboardFocus) { - UI::EnableFocusMovement(true); - } - UI::SetFocusedView(firstMatch); - } -} - void GameBrowser::LayoutChange(UI::EventParams &e) { *gridStyle_ = e.a == 0 ? true : false; Refresh(); diff --git a/UI/GameBrowser.h b/UI/GameBrowser.h index 593214d95f..05c843b066 100644 --- a/UI/GameBrowser.h +++ b/UI/GameBrowser.h @@ -24,22 +24,6 @@ enum class BrowseFlags { }; ENUM_CLASS_BITOPS(BrowseFlags); -enum class SearchState { - MATCH, - MISMATCH, - PENDING, -}; - -struct SearchEngine { - SearchBar *searchBar; - std::string searchFilter; - std::vector searchStates; - bool searchPending; - - void ApplySearchFilter(UI::ViewGroup *viewGroup, bool setKeyboardFocus); - bool Key(UI::ViewGroup *viewGroup, const KeyInput &input); -}; - class GameBrowser : public UI::LinearLayout { public: GameBrowser(int token, const Path &path, BrowseFlags browseFlags, bool portrait, bool *gridStyle, ScreenManager *screenManager, std::string_view lastText, std::string_view lastLink, UI::LayoutParams *layoutParams = nullptr); @@ -97,7 +81,7 @@ private: std::string lastText_; std::string lastLink_; - SearchEngine search_{}; + ViewSearch search_{}; Path focusGamePath_; bool listingPending_ = false; diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index e43b3df8d2..ada3cb601f 100644 --- a/UI/MiscViews.cpp +++ b/UI/MiscViews.cpp @@ -7,6 +7,8 @@ #include "Common/Data/Text/I18n.h" #include "Common/Math/curves.h" #include "Common/StringUtils.h" +#include "Common/Data/Encoding/Utf8.h" +#include "Common/UI/Root.h" #include "UI/MiscViews.h" #include "UI/GameInfoCache.h" #include "Common/UI/PopupScreens.h" @@ -350,3 +352,113 @@ bool SearchBar::Touch(const TouchInput &input) { } return retval; } + +void ViewSearch::ApplySearchFilter(UI::ViewGroup *viewGroup, bool setKeyboardFocus) { + if (searchBar) { + searchBar->SetSearchFilter(searchFilter); + searchBar->SetVisibility(searchFilter.empty() ? UI::V_GONE : UI::V_VISIBLE); + } + + if (searchFilter.empty() && searchStates.empty()) { + // We haven't hidden anything, and we're not searching, so do nothing. + searchPending = false; + return; + } + + std::string filter = NormalizeForSearch(searchFilter); + + searchPending = false; + // By default, everything is matching. + searchStates.resize(viewGroup->GetNumSubviews(), SearchState::MATCH); + if (filter.empty()) { + // Just quickly mark anything we hid as visible again. + for (int i = 0; i < viewGroup->GetNumSubviews(); ++i) { + UI::View *v = viewGroup->GetViewByIndex(i); + if (searchStates[i] != SearchState::MATCH) + v->SetVisibility(UI::V_VISIBLE); + } + + searchStates.clear(); + return; + } + + UI::View *firstMatch = nullptr; + + for (int i = 0; i < viewGroup->GetNumSubviews(); ++i) { + UI::View *v = viewGroup->GetViewByIndex(i); + std::string label = v->DescribeText(); + // This is a bit of a hack to recognize a pending game title. + if (label == "...") { + searchPending = true; + // Hide anything pending while, we'll pop-in search results as they match. + // Note: we leave it at MATCH if gone before, so we don't show it again. + if (v->GetVisibility() == UI::V_VISIBLE) { + if (searchStates[i] == SearchState::MATCH) + v->SetVisibility(UI::V_GONE); + searchStates[i] = SearchState::PENDING; + } + continue; + } + + label = NormalizeForSearch(label); + bool match = v->CanBeFocused() && label.find(filter) != label.npos; + if (match && !firstMatch) { + firstMatch = v; + } + if (match && searchStates[i] != SearchState::MATCH) { + // It was previously visible and force hidden, so show it again. + v->SetVisibility(UI::V_VISIBLE); + searchStates[i] = SearchState::MATCH; + } else if (!match && searchStates[i] == SearchState::MATCH && v->GetVisibility() == UI::V_VISIBLE) { + v->SetVisibility(UI::V_GONE); + searchStates[i] = SearchState::MISMATCH; + } + } + + if (firstMatch) { + if (setKeyboardFocus) { + UI::EnableFocusMovement(true); + } + UI::SetFocusedView(firstMatch); + } +} + +bool ViewSearch::Key(UI::ViewGroup *viewGroup, const KeyInput &input) { + bool retval = false; + // Only one is visible at a time, so we can just grab all Char input. + if (input.flags & KeyInputFlags::CHAR) { + const int unichar = input.keyCode; + if (unichar >= 0x20) { + // TODO: Save focus state here. + + // Insert it! (todo: do it with a string insert) + char buf[8]; + buf[u8_wc_toutf8(buf, unichar)] = '\0'; + searchFilter += buf; + ApplySearchFilter(viewGroup, true); + retval = true; + } + } else if (input.flags & KeyInputFlags::DOWN) { + if (input.keyCode == NKCODE_DEL) { + if (!searchFilter.empty()) { + searchFilter.pop_back(); + ApplySearchFilter(viewGroup, true); + retval = true; + if (searchFilter.empty()) { + // 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(); + ApplySearchFilter(viewGroup, false); + retval = true; + + // TODO: Restore focus state here. + UI::EnableFocusMovement(false); + } + } + return retval; +} diff --git a/UI/MiscViews.h b/UI/MiscViews.h index e1e25d1565..62e9dba49f 100644 --- a/UI/MiscViews.h +++ b/UI/MiscViews.h @@ -115,3 +115,19 @@ public: private: std::string searchFilter_ = "N/A"; }; + +enum class SearchState { + MATCH, + MISMATCH, + PENDING, +}; + +struct ViewSearch { + SearchBar *searchBar; + std::string searchFilter; + std::vector searchStates; + bool searchPending; + + void ApplySearchFilter(UI::ViewGroup *viewGroup, bool setKeyboardFocus); + bool Key(UI::ViewGroup *viewGroup, const KeyInput &input); +};