add layout options to grid based menues.

This commit is contained in:
ITotalJustice
2025-05-11 02:39:03 +01:00
parent 5d9e24af31
commit e279a70606
18 changed files with 579 additions and 244 deletions

View File

@@ -13,6 +13,7 @@
#include "yyjson_helper.hpp"
#include "swkbd.hpp"
#include "i18n.hpp"
#include "nro.hpp"
#include <minIni.h>
#include <string>
@@ -34,8 +35,6 @@ constexpr auto URL_JSON = "https://switch.cdn.fortheusers.org/repo.json";
constexpr auto URL_POST_FEEDBACK = "http://switchbru.com/appstore/feedback";
constexpr auto URL_GET_FEEDACK = "http://switchbru.com/appstore/feedback";
constexpr const char* INI_SECTION = "appstore";
constexpr const char* FILTER_STR[] = {
"All",
"Games",
@@ -846,7 +845,7 @@ void EntryMenu::SetIndex(s64 index) {
}
}
Menu::Menu() : MenuBase{"AppStore"_i18n} {
Menu::Menu() : grid::Menu{"AppStore"_i18n} {
fs::FsNativeSd fs;
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons");
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");
@@ -859,7 +858,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
if (m_is_search) {
SetSearch(m_search_term);
} else {
SetFilter(m_filter);
SetFilter();
}
SetIndex(m_entry_author_jump_back);
@@ -870,7 +869,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
}
} else if (m_is_search) {
m_is_search = false;
SetFilter(m_filter);
SetFilter();
SetIndex(m_entry_search_jump_back);
if (m_entry_search_jump_back >= 9) {
m_list->SetYoff(0);
@@ -913,17 +912,30 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){
SetFilter((Filter)index_out);
}, (s64)m_filter));
SidebarEntryArray::Items layout_items;
layout_items.push_back("List"_i18n);
layout_items.push_back("Icon"_i18n);
layout_items.push_back("Grid"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
SetSort((SortType)index_out);
}, (s64)m_sort));
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this](s64& index_out){
m_filter.Set(index_out);
SetFilter();
}, m_filter.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
SetOrder((OrderType)index_out);
}, (s64)m_order));
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
m_sort.Set(index_out);
SortAndFindLastFile();
}, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](s64& index_out){
m_order.Set(index_out);
SortAndFindLastFile();
}, m_order.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
m_layout.Set(index_out);
OnLayoutChange();
}, m_layout.Get()));
options->Add(std::make_shared<SidebarEntryCallback>("Search"_i18n, [this](){
std::string out;
@@ -953,14 +965,7 @@ Menu::Menu() : MenuBase{"AppStore"_i18n} {
}
});
m_filter = (Filter)ini_getl(INI_SECTION, "filter", m_filter, App::CONFIG_PATH);
m_sort = (SortType)ini_getl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH);
m_order = (OrderType)ini_getl(INI_SECTION, "order", m_order, App::CONFIG_PATH);
const Vec4 v{75, 110, 370, 155};
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
Sort();
OnLayoutChange();
}
Menu::~Menu() {
@@ -1055,42 +1060,27 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
}
auto text_id = ThemeEntryID_TEXT;
const auto selected = pos == m_index;
if (selected) {
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(x, y, w, h, ThemeEntryID_GRID);
}
const auto image_vec = DrawEntryNoImage(vg, theme, m_layout.Get(), v, selected, e.title.c_str(), e.author.c_str(), e.version.c_str());
constexpr double image_scale = 256.0 / 115.0;
// const float image_size = 256 / image_scale;
// const float image_size_h = 150 / image_scale;
DrawIcon(vg, e.image, m_default_image, x + 20, y + 20, 115, 115, true, image_scale);
const auto image_scale = 256.0 / image_vec.w;
DrawIcon(vg, e.image, m_default_image, image_vec.x, image_vec.y, image_vec.w, image_vec.h, true, image_scale);
// gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image);
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
const float font_size = 18;
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.title.c_str());
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.author.c_str());
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.version.c_str());
// todo: fix position on non-grid layout.
float i_size = 22;
switch (e.status) {
case EntryStatus::Get:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_get.image, 15);
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_get.image, 0);
break;
case EntryStatus::Installed:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_installed.image, 15);
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_installed.image, 0);
break;
case EntryStatus::Local:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_local.image, 15);
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_local.image, 0);
break;
case EntryStatus::Update:
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image, 15);
gfx::drawImage(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image, 0);
break;
}
});
@@ -1126,12 +1116,16 @@ void Menu::OnFocusGained() {
for (u32 i = 0; i < m_entries_current.size(); i++) {
if (current_entry.name == m_entries[m_entries_current[i]].name) {
SetIndex(i);
if (i >= 9) {
m_list->SetYoff((((i - 9) + 3) / 3) * m_list->GetMaxY());
const auto index = i;
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is
if (index >= page) {
m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else {
m_list->SetYoff(0);
}
SetIndex(i);
break;
}
}
@@ -1215,15 +1209,20 @@ void Menu::ScanHomebrew() {
index.shrink_to_fit();
}
SetFilter(Filter_All);
SetFilter();
SetIndex(0);
Sort();
}
void Menu::Sort() {
// log_write("doing sort: size: %zu count: %zu\n", repo_json.size(), m_entries.size());
const auto sort = m_sort.Get();
const auto order = m_order.Get();
const auto filter = m_filter.Get();
// returns true if lhs should be before rhs
const auto sorter = [this](EntryMini _lhs, EntryMini _rhs) -> bool {
const auto sorter = [this, sort, order](EntryMini _lhs, EntryMini _rhs) -> bool {
const auto& lhs = m_entries[_lhs];
const auto& rhs = m_entries[_rhs];
@@ -1241,11 +1240,11 @@ void Menu::Sort() {
} else if (!(lhs.status == EntryStatus::Local) && rhs.status == EntryStatus::Local) {
return false;
} else {
switch (m_sort) {
switch (sort) {
case SortType_Updated: {
if (lhs.updated_num == rhs.updated_num) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Descending) {
} else if (order == OrderType_Descending) {
return lhs.updated_num > rhs.updated_num;
} else {
return lhs.updated_num < rhs.updated_num;
@@ -1254,7 +1253,7 @@ void Menu::Sort() {
case SortType_Downloads: {
if (lhs.app_dls == rhs.app_dls) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Descending) {
} else if (order == OrderType_Descending) {
return lhs.app_dls > rhs.app_dls;
} else {
return lhs.app_dls < rhs.app_dls;
@@ -1263,14 +1262,14 @@ void Menu::Sort() {
case SortType_Size: {
if (lhs.extracted == rhs.extracted) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Descending) {
} else if (order == OrderType_Descending) {
return lhs.extracted > rhs.extracted;
} else {
return lhs.extracted < rhs.extracted;
}
} break;
case SortType_Alphabetical: {
if (m_order == OrderType_Descending) {
if (order == OrderType_Descending) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0;
@@ -1284,33 +1283,43 @@ void Menu::Sort() {
char subheader[128]{};
std::snprintf(subheader, sizeof(subheader), "Filter: %s | Sort: %s | Order: %s"_i18n.c_str(), i18n::get(FILTER_STR[m_filter]).c_str(), i18n::get(SORT_STR[m_sort]).c_str(), i18n::get(ORDER_STR[m_order]).c_str());
std::snprintf(subheader, sizeof(subheader), "Filter: %s | Sort: %s | Order: %s"_i18n.c_str(), i18n::get(FILTER_STR[filter]).c_str(), i18n::get(SORT_STR[sort]).c_str(), i18n::get(ORDER_STR[order]).c_str());
SetTitleSubHeading(subheader);
std::sort(m_entries_current.begin(), m_entries_current.end(), sorter);
}
void Menu::SetFilter(Filter filter) {
void Menu::SortAndFindLastFile() {
const auto name = GetEntry().name;
Sort();
SetIndex(0);
s64 index = -1;
for (u64 i = 0; i < m_entries_current.size(); i++) {
if (name == GetEntry(i).name) {
index = i;
break;
}
}
if (index >= 0) {
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is
if (index >= page) {
m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else {
m_list->SetYoff(0);
}
SetIndex(index);
}
}
void Menu::SetFilter() {
m_is_search = false;
m_is_author = false;
m_filter = filter;
m_entries_current = m_entries_index[m_filter];
ini_putl(INI_SECTION, "filter", m_filter, App::CONFIG_PATH);
SetIndex(0);
Sort();
}
void Menu::SetSort(SortType sort) {
m_sort = sort;
ini_putl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH);
SetIndex(0);
Sort();
}
void Menu::SetOrder(OrderType order) {
m_order = order;
ini_putl(INI_SECTION, "order", m_order, App::CONFIG_PATH);
m_entries_current = m_entries_index[m_filter.Get()];
SetIndex(0);
Sort();
}
@@ -1359,6 +1368,11 @@ void Menu::SetAuthor() {
Sort();
}
void Menu::OnLayoutChange() {
m_index = 0;
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
}
LazyImage::~LazyImage() {
if (image) {
nvgDeleteImage(App::GetVg(), image);

View File

@@ -113,7 +113,7 @@ void LaunchEntry(const Entry& e) {
} // namespace
Menu::Menu() : MenuBase{"Games"_i18n} {
Menu::Menu() : grid::Menu{"Games"_i18n} {
this->SetActions(
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
SetPop();
@@ -140,16 +140,26 @@ Menu::Menu() : MenuBase{"Games"_i18n} {
order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
SidebarEntryArray::Items layout_items;
layout_items.push_back("List"_i18n);
layout_items.push_back("Icon"_i18n);
layout_items.push_back("Grid"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
m_sort.Set(index_out);
SortAndFindLastFile(false);
}, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](s64& index_out){
m_order.Set(index_out);
SortAndFindLastFile(false);
}, m_order.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
m_layout.Set(index_out);
OnLayoutChange();
}, m_layout.Get()));
options->Add(std::make_shared<SidebarEntryBool>("Hide forwarders"_i18n, m_hide_forwarders.Get(), [this](bool& v_out){
m_hide_forwarders.Set(v_out);
m_dirty = true;
@@ -241,9 +251,7 @@ Menu::Menu() : MenuBase{"Games"_i18n} {
}})
);
const Vec4 v{75, 110, 370, 155};
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
OnLayoutChange();
nsInitialize();
nsGetApplicationRecordUpdateSystemEvent(&m_event);
@@ -285,7 +293,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
int image_load_count = 0;
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
const auto& [x, y, w, h] = v;
// const auto& [x, y, w, h] = v;
auto& e = m_entries[pos];
if (e.status == NacpLoadStatus::None) {
@@ -299,25 +307,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
}
auto text_id = ThemeEntryID_TEXT;
const auto selected = pos == m_index;
if (selected) {
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(v, ThemeEntryID_GRID);
}
const float image_size = 115;
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 5);
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
const float font_size = 18;
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetName());
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), e.GetDisplayVersion());
});
}
@@ -446,9 +437,11 @@ void Menu::SortAndFindLastFile(bool scan) {
}
if (index >= 0) {
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is
if (index >= 9) {
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY());
if (index >= page) {
m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else {
m_list->SetYoff(0);
}
@@ -466,4 +459,9 @@ void Menu::FreeEntries() {
m_entries.clear();
}
void Menu::OnLayoutChange() {
m_index = 0;
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
}
} // namespace sphaira::ui::menu::game

View File

@@ -0,0 +1,81 @@
#include "app.hpp"
#include "ui/menus/grid_menu_base.hpp"
#include "ui/nvg_util.hpp"
namespace sphaira::ui::menu::grid {
void Menu::DrawEntry(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version) {
DrawEntry(vg, theme, true, layout, v, selected, image, name, author, version);
}
Vec4 Menu::DrawEntryNoImage(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, const char* name, const char* author, const char* version) {
return DrawEntry(vg, theme, false, layout, v, selected, 0, name, author, version);
}
Vec4 Menu::DrawEntry(NVGcontext* vg, Theme* theme, bool draw_image, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version) {
const auto& [x, y, w, h] = v;
auto text_id = ThemeEntryID_TEXT;
if (selected) {
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(v, ThemeEntryID_GRID);
}
Vec4 image_v = v;
if (layout == LayoutType_GridDetail) {
image_v.x += 20;
image_v.y += 20;
image_v.w = 115;
image_v.h = 115;
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
const float font_size = 18;
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), name);
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), author);
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), version);
} else {
if (selected) {
gfx::drawAppLable(vg, theme, m_scroll_name, x, y, w, name);
}
}
if (draw_image) {
gfx::drawImage(vg, image_v, image ?: App::GetDefaultImage(), 5);
}
return image_v;
}
void Menu::OnLayoutChange(std::unique_ptr<List>& list, int layout) {
m_scroll_name.Reset();
m_scroll_author.Reset();
m_scroll_version.Reset();
switch (layout) {
case LayoutType_List: {
const Vec2 pad{14, 14};
const Vec4 v{106, 194, 256, 256};
list = std::make_unique<List>(1, 4, m_pos, v, pad);
list->SetLayout(List::Layout::HOME);
} break;
case LayoutType_Grid: {
const Vec2 pad{10, 10};
const Vec4 v{93, 186, 174, 174};
list = std::make_unique<List>(6, 6*2, m_pos, v, pad);
} break;
case LayoutType_GridDetail: {
const Vec2 pad{10, 10};
const Vec4 v{75, 110, 370, 155};
list = std::make_unique<List>(3, 3*3, m_pos, v, pad);
} break;
}
}
} // namespace sphaira::ui::menu::grid

View File

@@ -31,7 +31,7 @@ void FreeEntry(NVGcontext* vg, NroEntry& e) {
} // namespace
Menu::Menu() : MenuBase{"Homebrew"_i18n} {
Menu::Menu() : grid::Menu{"Homebrew"_i18n} {
this->SetActions(
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
nro_launch(m_entries[m_index].path);
@@ -57,6 +57,11 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n);
SidebarEntryArray::Items layout_items;
layout_items.push_back("List"_i18n);
layout_items.push_back("Icon"_i18n);
layout_items.push_back("Grid"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
m_sort.Set(index_out);
SortAndFindLastFile();
@@ -67,6 +72,11 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
SortAndFindLastFile();
}, m_order.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
m_layout.Set(index_out);
OnLayoutChange();
}, m_layout.Get()));
options->Add(std::make_shared<SidebarEntryBool>("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){
m_hide_sphaira.Set(enable);
}));
@@ -114,9 +124,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
}})
);
const Vec4 v{75, 110, 370, 155};
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
OnLayoutChange();
}
Menu::~Menu() {
@@ -143,7 +151,6 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
int image_load_count = 0;
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
const auto& [x, y, w, h] = v;
auto& e = m_entries[pos];
// lazy load image
@@ -161,21 +168,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
}
auto text_id = ThemeEntryID_TEXT;
const auto selected = pos == m_index;
if (selected) {
text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, theme, 4.f, v);
} else {
DrawElement(v, ThemeEntryID_GRID);
}
const float image_size = 115;
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 5);
const auto text_off = 148;
const auto text_x = x + text_off;
const auto text_clip_w = w - 30.f - text_off;
bool has_star = false;
if (IsStarEnabled()) {
if (!e.has_star.has_value()) {
@@ -184,10 +177,15 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
has_star = e.has_star.value();
}
const float font_size = 18;
m_scroll_name.DrawArgs(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s%s", has_star ? "\u2605 " : "", e.GetName());
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
std::string name;
if (has_star) {
name = std::string("\u2605 ") + e.GetName();
} else {
name = e.GetName();
}
const auto selected = pos == m_index;
DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, name.c_str(), e.GetAuthor(), e.GetDisplayVersion());
});
}
@@ -386,9 +384,11 @@ void Menu::SortAndFindLastFile() {
}
if (index >= 0) {
const auto row = m_list->GetRow();
const auto page = m_list->GetPage();
// guesstimate where the position is
if (index >= 9) {
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY());
if (index >= page) {
m_list->SetYoff((((index - page) + row) / row) * m_list->GetMaxY());
} else {
m_list->SetYoff(0);
}
@@ -406,6 +406,11 @@ void Menu::FreeEntries() {
m_entries.clear();
}
void Menu::OnLayoutChange() {
m_index = 0;
grid::Menu::OnLayoutChange(m_list, m_layout.Get());
}
Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) {
OwoConfig config{};
config.nro_path = path.toString();