diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index 607c528e87..a03104ad22 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -31,6 +31,7 @@ #include "Common/TimeUtil.h" #include "Common/StringUtils.h" #include "Common/System/OSD.h" +#include "Common/Data/Encoding/Utf8.h" #include "Core/System.h" #include "Core/Util/RecentFiles.h" #include "Core/HLE/sceCtrl.h" @@ -532,26 +533,65 @@ void GameBrowser::SetPath(const Path &path) { Refresh(); } -void GameBrowser::ApplySearchFilter(const std::string &filter) { - searchFilter_ = filter; - std::transform(searchFilter_.begin(), searchFilter_.end(), searchFilter_.begin(), tolower); +bool GameBrowser::Key(const KeyInput &input) { + bool retval = LinearLayout::Key(input); + if (retval) { + return true; + } + // 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) { + // Insert it! (todo: do it with a string insert) + char buf[8]; + buf[u8_wc_toutf8(buf, unichar)] = '\0'; + searchFilter_ += buf; + ApplySearchFilter(); + retval = true; + } + } else if (input.flags & KeyInputFlags::DOWN) { + if (input.keyCode == NKCODE_DEL) { + if (!searchFilter_.empty()) { + searchFilter_.pop_back(); + ApplySearchFilter(); + retval = true; + } + } else if (!searchFilter_.empty() && input.keyCode == NKCODE_ESCAPE) { + searchFilter_.clear(); + ApplySearchFilter(); + retval = true; + } + } + return retval; +} + +void GameBrowser::SetSearchFilter(const std::string &filter) { + searchFilter_ = filter; // We don't refresh because game info loads asynchronously anyway. ApplySearchFilter(); } void GameBrowser::ApplySearchFilter() { + 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 = searchFilter_; + std::transform(filter.begin(), filter.end(), filter.begin(), tolower); + searchPending_ = false; // By default, everything is matching. searchStates_.resize(gameList_->GetNumSubviews(), SearchState::MATCH); - if (searchFilter_.empty()) { + if (filter.empty()) { // Just quickly mark anything we hid as visible again. for (int i = 0; i < gameList_->GetNumSubviews(); ++i) { UI::View *v = gameList_->GetViewByIndex(i); @@ -581,7 +621,7 @@ void GameBrowser::ApplySearchFilter() { } std::transform(label.begin(), label.end(), label.begin(), tolower); - bool match = v->CanBeFocused() && label.find(searchFilter_) != label.npos; + bool match = v->CanBeFocused() && label.find(filter) != label.npos; if (match && searchStates_[i] != SearchState::MATCH) { // It was previously visible and force hidden, so show it again. v->SetVisibility(UI::V_VISIBLE); @@ -721,11 +761,27 @@ void GameBrowser::Draw(UIContext &dc) { view->Draw(dc); } } + if (clip_) { dc.PopScissor(); } } +void SearchBar::Draw(UIContext &dc) { + using namespace UI; + Bounds overlayBounds = bounds_.Inset(12, 0, 12, 0); + dc.FillRect(dc.GetTheme().itemStyle.background, overlayBounds); + dc.DrawText(searchFilter_, overlayBounds.x + 10, overlayBounds.centerY(), 0xFFFFFFFF, ALIGN_VCENTER); +} + +void SearchBar::GetContentDimensions(const UIContext &dc, float &w, float &h) const { + w = 0; + h = 0; + dc.MeasureText(dc.GetTheme().uiFont, 1.0f, 1.0f, searchFilter_, &w, &h); + w += 20; // Padding + h += 10; +} + static bool IsValidPBP(const Path &path, bool allowHomebrew) { if (!File::Exists(path)) return false; diff --git a/UI/GameBrowser.h b/UI/GameBrowser.h index da67132e0e..8fe203f409 100644 --- a/UI/GameBrowser.h +++ b/UI/GameBrowser.h @@ -23,6 +23,19 @@ enum class BrowseFlags { }; ENUM_CLASS_BITOPS(BrowseFlags); +class SearchBar : public UI::InertView { +public: + SearchBar(UI::LayoutParams *params) : UI::InertView(params) { SetVisibility(UI::Visibility::V_GONE); } + void Draw(UIContext &dc) override; + + void SetSearchFilter(std::string_view filter) { + searchFilter_ = filter; + } + void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; +private: + std::string searchFilter_ = "N/A"; +}; + 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); @@ -33,7 +46,11 @@ public: void FocusGame(const Path &gamePath); void SetPath(const Path &path); - void ApplySearchFilter(const std::string &filter); + void SetSearchBar(SearchBar *searchBar) { + searchBar_ = searchBar; + } + bool Key(const KeyInput &key) override; + void SetSearchFilter(const std::string &filter); void Draw(UIContext &dc) override; void Update() override; void RequestRefresh() { @@ -80,6 +97,7 @@ private: UI::ViewGroup *gameList_ = nullptr; PathBrowser path_; + SearchBar *searchBar_ = nullptr; bool *gridStyle_ = nullptr; BrowseFlags browseFlags_; std::string lastText_; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index bf285e9bb5..71ecdddce8 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -138,7 +138,10 @@ void MainScreen::CreateRecentTab() { using namespace UI; auto mm = GetI18NCategory(I18NCat::MAINMENU); - ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + LinearLayout *recentContainer = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + SearchBar *search = recentContainer->Add(new SearchBar(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + + ScrollView *scrollRecentGames = recentContainer->Add(new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); scrollRecentGames->SetTag("MainScreenRecentGames"); bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; @@ -146,11 +149,12 @@ void MainScreen::CreateRecentTab() { GameBrowser *tabRecentGames = new GameBrowser(GetRequesterToken(), Path("!RECENT"), BrowseFlags::NONE, portrait, &g_Config.bGridView1, screenManager(), "", "", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + tabRecentGames->SetSearchBar(search); scrollRecentGames->Add(tabRecentGames); gameBrowsers_.push_back(tabRecentGames); - tabHolder_->AddTab(mm->T("Recent"), ImageID::invalid(), scrollRecentGames); + tabHolder_->AddTab(mm->T("Recent"), ImageID::invalid(), recentContainer); tabRecentGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant); tabRecentGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected); tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight); @@ -572,8 +576,11 @@ void MainScreen::update() { UpdateUIState(UISTATE_MENU); if (searchChanged_) { - for (auto browser : gameBrowsers_) - browser->ApplySearchFilter(searchFilter_); + for (auto browser : gameBrowsers_) { + if (browser->GetVisibility() == UI::V_VISIBLE) { + browser->SetSearchFilter(searchFilter_); + } + } searchChanged_ = false; } }