Break out SearchEngine and SearchBar

This commit is contained in:
Henrik Rydgård
2026-05-03 17:41:18 +02:00
parent 540f0d6267
commit e354d0dfdd
5 changed files with 125 additions and 109 deletions
+1 -1
View File
@@ -340,7 +340,7 @@ bool ChoiceStrip::Key(const KeyInput &input) {
if (selected_ > 0) {
SetSelection(selected_ - 1, true);
} else if (!choices_.empty()) {
SetSelection(choices_.size() - 1, true);
SetSelection((int)choices_.size() - 1, true);
}
} else {
if (selected_ < (int)choices_.size() - 1) {
+49 -85
View File
@@ -462,7 +462,7 @@ std::string GameButton::DescribeText() const {
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO))
return "...";
auto u = GetI18NCategory(I18NCat::UI_ELEMENTS);
return ApplySafeSubstitutions(u->T("%1 button"), ginfo->GetTitle());
return ginfo->GetTitle();
}
class DirButton : public UI::Button {
@@ -486,6 +486,10 @@ public:
pinned_ = pin;
}
std::string DescribeText() const {
return path_.GetFilename();
}
private:
Path path_;
bool gridStyle_;
@@ -551,6 +555,11 @@ bool GameBrowser::Key(const KeyInput &input) {
return true;
}
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;
@@ -560,26 +569,26 @@ bool GameBrowser::Key(const KeyInput &input) {
// Insert it! (todo: do it with a string insert)
char buf[8];
buf[u8_wc_toutf8(buf, unichar)] = '\0';
searchFilter_ += buf;
ApplySearchFilter(true);
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(true);
if (!searchFilter.empty()) {
searchFilter.pop_back();
ApplySearchFilter(viewGroup, true);
retval = true;
if (searchFilter_.empty()) {
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(false);
} else if (!searchFilter.empty() && input.keyCode == NKCODE_ESCAPE) {
searchFilter.clear();
ApplySearchFilter(viewGroup, false);
retval = true;
// TODO: Restore focus state here.
@@ -590,56 +599,54 @@ bool GameBrowser::Key(const KeyInput &input) {
}
void GameBrowser::SetSearchFilter(const std::string &filter, bool setKeyboardFocus) {
searchFilter_ = filter;
search_.searchFilter = filter;
// We don't refresh because game info loads asynchronously anyway.
ApplySearchFilter(setKeyboardFocus);
search_.ApplySearchFilter(gameList_, setKeyboardFocus);
}
void GameBrowser::ApplySearchFilter(bool setKeyboardFocus) {
if (searchBar_) {
searchBar_->SetSearchFilter(searchFilter_);
searchBar_->SetVisibility(searchFilter_.empty() ? UI::V_GONE : UI::V_VISIBLE);
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()) {
if (searchFilter.empty() && searchStates.empty()) {
// We haven't hidden anything, and we're not searching, so do nothing.
searchPending_ = false;
searchPending = false;
return;
}
std::string filter = NormalizeForSearch(searchFilter_);
std::string filter = NormalizeForSearch(searchFilter);
searchPending_ = false;
searchPending = false;
// By default, everything is matching.
searchStates_.resize(gameList_->GetNumSubviews(), SearchState::MATCH);
searchStates.resize(viewGroup->GetNumSubviews(), SearchState::MATCH);
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);
if (searchStates_[i] != SearchState::MATCH)
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();
searchStates.clear();
return;
}
View *firstMatch = nullptr;
UI::View *firstMatch = nullptr;
for (int i = 0; i < gameList_->GetNumSubviews(); ++i) {
UI::View *v = gameList_->GetViewByIndex(i);
for (int i = 0; i < viewGroup->GetNumSubviews(); ++i) {
UI::View *v = viewGroup->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;
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)
if (searchStates[i] == SearchState::MATCH)
v->SetVisibility(UI::V_GONE);
searchStates_[i] = SearchState::PENDING;
searchStates[i] = SearchState::PENDING;
}
continue;
}
@@ -649,13 +656,13 @@ void GameBrowser::ApplySearchFilter(bool setKeyboardFocus) {
if (match && !firstMatch) {
firstMatch = v;
}
if (match && searchStates_[i] != SearchState::MATCH) {
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) {
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;
searchStates[i] = SearchState::MISMATCH;
}
}
@@ -762,8 +769,8 @@ void GameBrowser::Update() {
Refresh();
refreshPending_ = false;
}
if (searchPending_) {
ApplySearchFilter(false);
if (search_.searchPending) {
search_.ApplySearchFilter(gameList_, false);
}
}
@@ -801,52 +808,9 @@ void GameBrowser::Draw(UIContext &dc) {
}
}
void SearchBar::Draw(UIContext &dc) {
using namespace UI;
dc.FillRect(dc.GetTheme().itemStyle.background, bounds_);
const ImageID searchIcon = ImageID("I_SEARCH");
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(searchIcon);
int leftMargin = 10;
if (image) {
dc.Draw()->DrawImage(searchIcon, bounds_.x + leftMargin, bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_VCENTER);
leftMargin += image->w + 10;
}
dc.DrawText(searchFilter_, bounds_.x + leftMargin, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER);
}
void SearchBar::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
w = 0;
h = 0;
const ImageID searchIcon = ImageID("I_SEARCH");
dc.MeasureText(dc.GetTheme().uiFont, 1.0f, 1.0f, searchFilter_, &w, &h);
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(searchIcon);
if (image) {
w += image->w + 10;
h = std::max(h, (float)image->h);
}
w += 20; // Padding
h += 24;
}
bool SearchBar::Touch(const TouchInput &input) {
bool retval = UI::InertView::Touch(input);
// Search bar has a simple touch-to-cancel functionality,
// for the user to be able to get out of searches without knowing ESC (or backspacing the whole search string).
if (input.flags & TouchInputFlags::DOWN) {
if (bounds_.Contains(input.x, input.y)) {
UI::EventParams params{this};
OnCancel.Trigger(params);
return true;
}
}
return retval;
}
void GameBrowser::SetSearchBar(SearchBar *searchBar) {
searchBar_ = searchBar;
searchBar_->OnCancel.Add([this](UI::EventParams &) {
search_.searchBar = searchBar;
search_.searchBar->OnCancel.Add([this](UI::EventParams &) {
SetSearchFilter("", false);
});
}
@@ -880,7 +844,7 @@ void GameBrowser::Refresh() {
// Kill all the contents
Clear();
searchStates_.clear();
search_.searchStates.clear();
Add(new Spacer(1.0f));
auto mm = GetI18NCategory(I18NCat::MAINMENU);
+16 -23
View File
@@ -6,6 +6,7 @@
#include "Common/File/Path.h"
#include "Common/UI/ViewGroup.h"
#include "Common/File/PathBrowser.h"
#include "UI/MiscViews.h"
enum GameBrowserFlags {
FLAG_HOMEBREWSTOREBUTTON = 1
@@ -23,20 +24,20 @@ 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;
enum class SearchState {
MATCH,
MISMATCH,
PENDING,
};
bool Touch(const TouchInput &input) override;
void SetSearchFilter(std::string_view filter) {
searchFilter_ = filter;
}
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
struct SearchEngine {
SearchBar *searchBar;
std::string searchFilter;
std::vector<SearchState> searchStates;
bool searchPending;
UI::Event OnCancel;
private:
std::string searchFilter_ = "N/A";
void ApplySearchFilter(UI::ViewGroup *viewGroup, bool setKeyboardFocus);
bool Key(UI::ViewGroup *viewGroup, const KeyInput &input);
};
class GameBrowser : public UI::LinearLayout {
@@ -66,7 +67,6 @@ protected:
virtual bool DisplayTopBar();
virtual bool HasSpecialFiles(std::vector<Path> &filenames);
virtual Path HomePath();
void ApplySearchFilter(bool setKeyboardFocus);
void Refresh();
@@ -90,24 +90,17 @@ private:
void OnRecentClear(UI::EventParams &e);
void OnHomebrewStore(UI::EventParams &e);
enum class SearchState {
MATCH,
MISMATCH,
PENDING,
};
UI::ViewGroup *gameList_ = nullptr;
PathBrowser path_;
SearchBar *searchBar_ = nullptr;
bool *gridStyle_ = nullptr;
BrowseFlags browseFlags_;
std::string lastText_;
std::string lastLink_;
std::string searchFilter_;
std::vector<SearchState> searchStates_;
SearchEngine search_{};
Path focusGamePath_;
bool listingPending_ = false;
bool searchPending_ = false;
bool refreshPending_ = false;
float lastScale_ = 1.0f;
bool lastLayoutWasGrid_ = true;
+43
View File
@@ -307,3 +307,46 @@ void SettingHint::Draw(UIContext &dc) {
dc.FillRect(style.background, bounds_);
UI::TextView::Draw(dc);
}
void SearchBar::Draw(UIContext &dc) {
using namespace UI;
dc.FillRect(dc.GetTheme().itemStyle.background, bounds_);
const ImageID searchIcon = ImageID("I_SEARCH");
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(searchIcon);
int leftMargin = 10;
if (image) {
dc.Draw()->DrawImage(searchIcon, bounds_.x + leftMargin, bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_VCENTER);
leftMargin += image->w + 10;
}
dc.DrawText(searchFilter_, bounds_.x + leftMargin, bounds_.centerY(), 0xFFFFFFFF, ALIGN_VCENTER);
}
void SearchBar::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
w = 0;
h = 0;
const ImageID searchIcon = ImageID("I_SEARCH");
dc.MeasureText(dc.GetTheme().uiFont, 1.0f, 1.0f, searchFilter_, &w, &h);
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(searchIcon);
if (image) {
w += image->w + 10;
h = std::max(h, (float)image->h);
}
w += 20; // Padding
h += 24;
}
bool SearchBar::Touch(const TouchInput &input) {
bool retval = UI::InertView::Touch(input);
// Search bar has a simple touch-to-cancel functionality,
// for the user to be able to get out of searches without knowing ESC (or backspacing the whole search string).
if (input.flags & TouchInputFlags::DOWN) {
if (bounds_.Contains(input.x, input.y)) {
UI::EventParams params{this};
OnCancel.Trigger(params);
return true;
}
}
return retval;
}
+16
View File
@@ -99,3 +99,19 @@ private:
};
void AddRotationPicker(ScreenManager *screenManager, UI::ViewGroup *parent, bool text);
class SearchBar : public UI::InertView {
public:
SearchBar(UI::LayoutParams *params) : UI::InertView(params) { SetVisibility(UI::Visibility::V_GONE); }
void Draw(UIContext &dc) override;
bool Touch(const TouchInput &input) override;
void SetSearchFilter(std::string_view filter) {
searchFilter_ = filter;
}
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
UI::Event OnCancel;
private:
std::string searchFilter_ = "N/A";
};