UI: Add a very simplistic search to game browser.

For now, no real UI (which might include a search icon, no matches
message, banner that you're searching, etc.)  Press Ctrl-F to filter.
This commit is contained in:
Unknown W. Brackets 2023-04-29 11:45:30 -07:00
parent ce0324b611
commit 19b15de2d5
2 changed files with 111 additions and 0 deletions

View File

@ -429,6 +429,9 @@ void GameButton::Draw(UIContext &dc) {
std::string GameButton::DescribeText() const {
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0);
if (ginfo->pending)
return "...";
auto u = GetI18NCategory(I18NCat::UI_ELEMENTS);
return ReplaceAll(u->T("%1 button"), "%1", ginfo->GetTitle());
}
@ -528,6 +531,67 @@ 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);
// We don't refresh because game info loads asynchronously anyway.
ApplySearchFilter();
}
void GameBrowser::ApplySearchFilter() {
if (searchFilter_.empty() && searchStates_.empty()) {
// We haven't hidden anything, and we're not searching, so do nothing.
searchPending_ = false;
return;
}
searchPending_ = false;
// By default, everything is matching.
searchStates_.resize(gameList_->GetNumSubviews(), SearchState::MATCH);
if (searchFilter_.empty()) {
// Just quickly mark anything we hid as visible again.
for (int i = 0; i < gameList_->GetNumSubviews(); ++i) {
UI::View *v = gameList_->GetViewByIndex(i);
if (searchStates_[i] != SearchState::MATCH)
v->SetVisibility(UI::V_VISIBLE);
}
searchStates_.clear();
return;
}
for (int i = 0; i < gameList_->GetNumSubviews(); ++i) {
UI::View *v = gameList_->GetViewByIndex(i);
std::string label = v->DescribeText();
// TODO: Maybe we should just save the gameButtons list, though nice to search dirs too?
// 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;
}
std::transform(label.begin(), label.end(), label.begin(), tolower);
bool match = v->CanBeFocused() && label.find(searchFilter_) != label.npos;
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;
}
}
}
UI::EventReturn GameBrowser::LayoutChange(UI::EventParams &e) {
*gridStyle_ = e.a == 0 ? true : false;
Refresh();
@ -622,6 +686,9 @@ void GameBrowser::Update() {
if (listingPending_ && path_.IsListingReady()) {
Refresh();
}
if (searchPending_) {
ApplySearchFilter();
}
}
void GameBrowser::Draw(UIContext &dc) {
@ -686,6 +753,7 @@ void GameBrowser::Refresh() {
// Kill all the contents
Clear();
searchStates_.clear();
Add(new Spacer(1.0f));
auto mm = GetI18NCategory(I18NCat::MAINMENU);
@ -1219,6 +1287,27 @@ void MainScreen::CreateViews() {
}
}
bool MainScreen::key(const KeyInput &touch) {
if (touch.flags & KEY_DOWN) {
if (touch.keyCode == NKCODE_CTRL_LEFT || touch.keyCode == NKCODE_CTRL_RIGHT)
searchKeyModifier_ = true;
if (touch.keyCode == NKCODE_F && searchKeyModifier_) {
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__)
auto se = GetI18NCategory(I18NCat::SEARCH);
System_InputBoxGetString(se->T("Search term"), searchFilter_, [&](const std::string &value, int) {
searchFilter_ = StripSpaces(value);
searchChanged_ = true;
});
#endif
}
} else if (touch.flags & KEY_UP) {
if (touch.keyCode == NKCODE_CTRL_LEFT || touch.keyCode == NKCODE_CTRL_RIGHT)
searchKeyModifier_ = false;
}
return UIScreenWithBackground::key(touch);
}
UI::EventReturn MainScreen::OnAllowStorage(UI::EventParams &e) {
System_AskForPermission(SYSTEM_PERMISSION_STORAGE);
return UI::EVENT_DONE;
@ -1276,6 +1365,12 @@ void MainScreen::sendMessage(const char *message, const char *value) {
void MainScreen::update() {
UIScreen::update();
UpdateUIState(UISTATE_MENU);
if (searchChanged_) {
for (auto browser : gameBrowsers_)
browser->ApplySearchFilter(searchFilter_);
searchChanged_ = false;
}
}
UI::EventReturn MainScreen::OnLoadFile(UI::EventParams &e) {

View File

@ -51,6 +51,7 @@ public:
void FocusGame(const Path &gamePath);
void SetPath(const Path &path);
void ApplySearchFilter(const std::string &filter);
void Draw(UIContext &dc) override;
void Update() override;
@ -58,6 +59,7 @@ protected:
virtual bool DisplayTopBar();
virtual bool HasSpecialFiles(std::vector<Path> &filenames);
virtual Path HomePath();
void ApplySearchFilter();
void Refresh();
@ -80,14 +82,23 @@ private:
UI::EventReturn OnRecentClear(UI::EventParams &e);
UI::EventReturn OnHomebrewStore(UI::EventParams &e);
enum class SearchState {
MATCH,
MISMATCH,
PENDING,
};
UI::ViewGroup *gameList_ = nullptr;
PathBrowser path_;
bool *gridStyle_ = nullptr;
BrowseFlags browseFlags_;
std::string lastText_;
std::string lastLink_;
std::string searchFilter_;
std::vector<SearchState> searchStates_;
Path focusGamePath_;
bool listingPending_ = false;
bool searchPending_ = false;
float lastScale_ = 1.0f;
bool lastLayoutWasGrid_ = true;
ScreenManager *screenManager_;
@ -107,6 +118,8 @@ public:
// Horrible hack to show the demos & homebrew tab after having installed a game from a zip file.
static bool showHomebrewTab;
bool key(const KeyInput &touch) override;
protected:
void CreateViews() override;
void DrawBackground(UIContext &dc) override;
@ -148,6 +161,9 @@ protected:
bool lastVertical_;
bool confirmedTemporary_ = false;
UI::ScrollView *scrollAllGames_ = nullptr;
bool searchKeyModifier_ = false;
bool searchChanged_ = false;
std::string searchFilter_;
friend class RemoteISOBrowseScreen;
};