option to hide homebrew.
This commit is contained in:
@@ -190,7 +190,7 @@ FetchContent_Declare(yyjson
|
|||||||
|
|
||||||
FetchContent_Declare(minIni
|
FetchContent_Declare(minIni
|
||||||
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
|
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
|
||||||
GIT_TAG 11cac8b
|
GIT_TAG 6e952b6
|
||||||
)
|
)
|
||||||
|
|
||||||
FetchContent_Declare(zstd
|
FetchContent_Declare(zstd
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace sphaira {
|
|||||||
|
|
||||||
struct Hbini {
|
struct Hbini {
|
||||||
u64 timestamp{}; // timestamp of last launch
|
u64 timestamp{}; // timestamp of last launch
|
||||||
|
bool hidden{};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MiniNacp {
|
struct MiniNacp {
|
||||||
@@ -61,7 +62,7 @@ auto nro_parse(const fs::FsPath& path, NroEntry& entry) -> Result;
|
|||||||
* nro found.
|
* nro found.
|
||||||
* this does nothing if nested=false.
|
* this does nothing if nested=false.
|
||||||
*/
|
*/
|
||||||
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool hide_spahira, bool nested = true, bool scan_all_dir = true) -> Result;
|
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool nested = true, bool scan_all_dir = true) -> Result;
|
||||||
|
|
||||||
auto nro_get_icon(const fs::FsPath& path, u64 size, u64 offset) -> std::vector<u8>;
|
auto nro_get_icon(const fs::FsPath& path, u64 size, u64 offset) -> std::vector<u8>;
|
||||||
auto nro_get_icon(const fs::FsPath& path) -> std::vector<u8>;
|
auto nro_get_icon(const fs::FsPath& path) -> std::vector<u8>;
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
|
|
||||||
namespace sphaira::ui::menu::homebrew {
|
namespace sphaira::ui::menu::homebrew {
|
||||||
|
|
||||||
|
enum Filter {
|
||||||
|
Filter_All,
|
||||||
|
Filter_HideHidden,
|
||||||
|
Filter_MAX,
|
||||||
|
};
|
||||||
|
|
||||||
enum SortType {
|
enum SortType {
|
||||||
SortType_Updated,
|
SortType_Updated,
|
||||||
SortType_Alphabetical,
|
SortType_Alphabetical,
|
||||||
@@ -43,6 +49,14 @@ struct Menu final : grid::Menu {
|
|||||||
static Result InstallHomebrew(const fs::FsPath& path, const std::vector<u8>& icon);
|
static Result InstallHomebrew(const fs::FsPath& path, const std::vector<u8>& icon);
|
||||||
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
||||||
|
|
||||||
|
auto GetEntry(s64 i) -> NroEntry& {
|
||||||
|
return m_entries[m_entries_current[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetEntry() -> NroEntry& {
|
||||||
|
return GetEntry(m_index);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SetIndex(s64 index);
|
void SetIndex(s64 index);
|
||||||
void InstallHomebrew();
|
void InstallHomebrew();
|
||||||
@@ -51,6 +65,7 @@ private:
|
|||||||
void SortAndFindLastFile(bool scan = false);
|
void SortAndFindLastFile(bool scan = false);
|
||||||
void FreeEntries();
|
void FreeEntries();
|
||||||
void OnLayoutChange();
|
void OnLayoutChange();
|
||||||
|
void DisplayOptions();
|
||||||
|
|
||||||
auto IsStarEnabled() -> bool {
|
auto IsStarEnabled() -> bool {
|
||||||
return m_sort.Get() >= SortType_UpdatedStar;
|
return m_sort.Get() >= SortType_UpdatedStar;
|
||||||
@@ -60,6 +75,9 @@ private:
|
|||||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||||
|
|
||||||
std::vector<NroEntry> m_entries{};
|
std::vector<NroEntry> m_entries{};
|
||||||
|
std::vector<u32> m_entries_index[Filter_MAX]{};
|
||||||
|
std::span<u32> m_entries_current{};
|
||||||
|
|
||||||
s64 m_index{}; // where i am in the array
|
s64 m_index{}; // where i am in the array
|
||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
bool m_dirty{};
|
bool m_dirty{};
|
||||||
@@ -67,7 +85,7 @@ private:
|
|||||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
||||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||||
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
|
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
|
||||||
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
|
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::homebrew
|
} // namespace sphaira::ui::menu::homebrew
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ auto nro_parse_internal(fs::Fs* fs, const fs::FsPath& path, NroEntry& entry) ->
|
|||||||
// this function is recursive by 1 level deep
|
// this function is recursive by 1 level deep
|
||||||
// if the nro is in switch/folder/folder2/app.nro it will NOT be found
|
// if the nro is in switch/folder/folder2/app.nro it will NOT be found
|
||||||
// switch/folder/app.nro for example will work fine.
|
// switch/folder/app.nro for example will work fine.
|
||||||
auto nro_scan_internal(fs::Fs* fs, const fs::FsPath& path, std::vector<NroEntry>& nros, bool hide_sphaira, bool nested, bool scan_all_dir, bool root) -> Result {
|
auto nro_scan_internal(fs::Fs* fs, const fs::FsPath& path, std::vector<NroEntry>& nros, bool nested, bool scan_all_dir, bool root) -> Result {
|
||||||
// we don't need to scan for folders if we are not root
|
// we don't need to scan for folders if we are not root
|
||||||
u32 dir_open_type = FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize;
|
u32 dir_open_type = FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize;
|
||||||
if (root) {
|
if (root) {
|
||||||
@@ -99,11 +99,6 @@ auto nro_scan_internal(fs::Fs* fs, const fs::FsPath& path, std::vector<NroEntry>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip self
|
|
||||||
if (hide_sphaira && !strncmp(e.name, "sphaira", strlen("sphaira"))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.type == FsDirEntryType_Dir) {
|
if (e.type == FsDirEntryType_Dir) {
|
||||||
// assert(!root && "dir should only be scanned on non-root!");
|
// assert(!root && "dir should only be scanned on non-root!");
|
||||||
fs::FsPath fullpath;
|
fs::FsPath fullpath;
|
||||||
@@ -117,7 +112,7 @@ auto nro_scan_internal(fs::Fs* fs, const fs::FsPath& path, std::vector<NroEntry>
|
|||||||
} else {
|
} else {
|
||||||
// slow path...
|
// slow path...
|
||||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path.s, e.name);
|
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path.s, e.name);
|
||||||
nro_scan_internal(fs, fullpath, nros, hide_sphaira, nested, scan_all_dir, false);
|
nro_scan_internal(fs, fullpath, nros, nested, scan_all_dir, false);
|
||||||
}
|
}
|
||||||
} else if (e.type == FsDirEntryType_File && std::string_view{e.name}.ends_with(".nro")) {
|
} else if (e.type == FsDirEntryType_File && std::string_view{e.name}.ends_with(".nro")) {
|
||||||
fs::FsPath fullpath;
|
fs::FsPath fullpath;
|
||||||
@@ -139,9 +134,9 @@ auto nro_scan_internal(fs::Fs* fs, const fs::FsPath& path, std::vector<NroEntry>
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool hide_sphaira, bool nested, bool scan_all_dir, bool root) -> Result {
|
auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool nested, bool scan_all_dir, bool root) -> Result {
|
||||||
fs::FsNativeSd fs;
|
fs::FsNativeSd fs;
|
||||||
return nro_scan_internal(&fs, path, nros, hide_sphaira, nested, scan_all_dir, root);
|
return nro_scan_internal(&fs, path, nros, nested, scan_all_dir, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nro_get_icon_internal(fs::File* f, u64 size, u64 offset) -> std::vector<u8> {
|
auto nro_get_icon_internal(fs::File* f, u64 size, u64 offset) -> std::vector<u8> {
|
||||||
@@ -198,8 +193,8 @@ auto nro_parse(const fs::FsPath& path, NroEntry& entry) -> Result {
|
|||||||
return nro_parse_internal(&fs, path, entry);
|
return nro_parse_internal(&fs, path, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool hide_sphaira, bool nested, bool scan_all_dir) -> Result {
|
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool nested, bool scan_all_dir) -> Result {
|
||||||
return nro_scan_internal(path, nros, hide_sphaira, nested, scan_all_dir, true);
|
return nro_scan_internal(path, nros, nested, scan_all_dir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nro_get_icon(const fs::FsPath& path, u64 size, u64 offset) -> std::vector<u8> {
|
auto nro_get_icon(const fs::FsPath& path, u64 size, u64 offset) -> std::vector<u8> {
|
||||||
|
|||||||
@@ -8,28 +8,6 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
namespace sphaira::option {
|
namespace sphaira::option {
|
||||||
namespace {
|
|
||||||
|
|
||||||
// these are taken from minini in order to parse a value already loaded in memory.
|
|
||||||
long getl(const char* LocalBuffer, long def) {
|
|
||||||
const auto len = strlen(LocalBuffer);
|
|
||||||
return (len == 0) ? def
|
|
||||||
: ((len >= 2 && toupper((int)LocalBuffer[1]) == 'X') ? strtol(LocalBuffer, NULL, 16)
|
|
||||||
: strtol(LocalBuffer, NULL, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getbool(const char* LocalBuffer, bool def) {
|
|
||||||
const auto c = toupper(LocalBuffer[0]);
|
|
||||||
|
|
||||||
if (c == 'Y' || c == '1' || c == 'T')
|
|
||||||
return true;
|
|
||||||
else if (c == 'N' || c == '0' || c == 'F')
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
auto OptionBase<T>::GetInternal(const char* name) -> T {
|
auto OptionBase<T>::GetInternal(const char* name) -> T {
|
||||||
@@ -90,9 +68,9 @@ auto OptionBase<T>::LoadFrom(const char* name, const char* value) -> bool {
|
|||||||
if (m_name == name) {
|
if (m_name == name) {
|
||||||
if (m_file) {
|
if (m_file) {
|
||||||
if constexpr(std::is_same_v<T, bool>) {
|
if constexpr(std::is_same_v<T, bool>) {
|
||||||
m_value = getbool(value, m_default_value);
|
m_value = ini_parse_getbool(value, m_default_value);
|
||||||
} else if constexpr(std::is_same_v<T, long>) {
|
} else if constexpr(std::is_same_v<T, long>) {
|
||||||
m_value = getl(value, m_default_value);
|
m_value = ini_parse_getl(value, m_default_value);
|
||||||
} else if constexpr(std::is_same_v<T, std::string>) {
|
} else if constexpr(std::is_same_v<T, std::string>) {
|
||||||
m_value = value;
|
m_value = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,93 +53,10 @@ Menu::Menu() : grid::Menu{"Homebrew"_i18n, MenuFlag_Tab} {
|
|||||||
|
|
||||||
this->SetActions(
|
this->SetActions(
|
||||||
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
|
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
|
||||||
nro_launch(m_entries[m_index].path);
|
nro_launch(GetEntry().path);
|
||||||
}}),
|
}}),
|
||||||
std::make_pair(Button::X, Action{"Options"_i18n, [this](){
|
std::make_pair(Button::X, Action{"Options"_i18n, [this](){
|
||||||
auto options = std::make_unique<Sidebar>("Homebrew Options"_i18n, Sidebar::Side::RIGHT);
|
DisplayOptions();
|
||||||
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
|
||||||
|
|
||||||
if (m_entries.size()) {
|
|
||||||
options->Add<SidebarEntryCallback>("Sort By"_i18n, [this](){
|
|
||||||
auto options = std::make_unique<Sidebar>("Sort Options"_i18n, Sidebar::Side::RIGHT);
|
|
||||||
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
|
||||||
|
|
||||||
SidebarEntryArray::Items sort_items;
|
|
||||||
sort_items.push_back("Updated"_i18n);
|
|
||||||
sort_items.push_back("Alphabetical"_i18n);
|
|
||||||
sort_items.push_back("Size"_i18n);
|
|
||||||
sort_items.push_back("Updated (Star)"_i18n);
|
|
||||||
sort_items.push_back("Alphabetical (Star)"_i18n);
|
|
||||||
sort_items.push_back("Size (Star)"_i18n);
|
|
||||||
|
|
||||||
SidebarEntryArray::Items order_items;
|
|
||||||
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<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
|
||||||
m_sort.Set(index_out);
|
|
||||||
SortAndFindLastFile();
|
|
||||||
}, m_sort.Get());
|
|
||||||
|
|
||||||
options->Add<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
|
|
||||||
m_order.Set(index_out);
|
|
||||||
SortAndFindLastFile();
|
|
||||||
}, m_order.Get());
|
|
||||||
|
|
||||||
options->Add<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
|
|
||||||
m_layout.Set(index_out);
|
|
||||||
OnLayoutChange();
|
|
||||||
}, m_layout.Get());
|
|
||||||
|
|
||||||
options->Add<SidebarEntryBool>("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){
|
|
||||||
m_hide_sphaira.Set(enable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
options->Add<SidebarEntryCallback>("Info"_i18n, [this](){
|
|
||||||
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
options->Add<SidebarEntryCallback>("Delete"_i18n, [this](){
|
|
||||||
const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].path.toString() + "?";
|
|
||||||
App::Push<OptionBox>(
|
|
||||||
buf,
|
|
||||||
"Back"_i18n, "Delete"_i18n, 1, [this](auto op_index){
|
|
||||||
if (op_index && *op_index) {
|
|
||||||
if (R_SUCCEEDED(fs::FsNativeSd().DeleteFile(m_entries[m_index].path))) {
|
|
||||||
FreeEntry(App::GetVg(), m_entries[m_index]);
|
|
||||||
m_entries.erase(m_entries.begin() + m_index);
|
|
||||||
SetIndex(m_index ? m_index - 1 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, m_entries[m_index].image
|
|
||||||
);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
auto forwarder_entry = options->Add<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){
|
|
||||||
if (App::GetInstallPrompt()) {
|
|
||||||
App::Push<OptionBox>(
|
|
||||||
"WARNING: Installing forwarders will lead to a ban!"_i18n,
|
|
||||||
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
|
|
||||||
if (op_index && *op_index) {
|
|
||||||
InstallHomebrew();
|
|
||||||
}
|
|
||||||
}, m_entries[m_index].image
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
InstallHomebrew();
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
forwarder_entry->Depends(App::GetInstallEnable, i18n::get(App::INSTALL_DEPENDS_STR));
|
|
||||||
}
|
|
||||||
}})
|
}})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -179,8 +96,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
const int image_load_max = 2;
|
const int image_load_max = 2;
|
||||||
int image_load_count = 0;
|
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) {
|
m_list->Draw(vg, theme, m_entries_current.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||||
auto& e = m_entries[pos];
|
const auto index = m_entries_current[pos];
|
||||||
|
auto& e = m_entries[index];
|
||||||
|
|
||||||
// lazy load image
|
// lazy load image
|
||||||
if (image_load_count < image_load_max) {
|
if (image_load_count < image_load_max) {
|
||||||
@@ -240,17 +158,17 @@ void Menu::SetIndex(s64 index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsStarEnabled()) {
|
if (IsStarEnabled()) {
|
||||||
const auto star_path = GenerateStarPath(m_entries[m_index].path);
|
const auto star_path = GenerateStarPath(GetEntry().path);
|
||||||
if (fs::FsNativeSd().FileExists(star_path)) {
|
if (fs::FsNativeSd().FileExists(star_path)) {
|
||||||
SetAction(Button::R3, Action{"Unstar"_i18n, [this](){
|
SetAction(Button::R3, Action{"Unstar"_i18n, [this](){
|
||||||
fs::FsNativeSd().DeleteFile(GenerateStarPath(m_entries[m_index].path));
|
fs::FsNativeSd().DeleteFile(GenerateStarPath(GetEntry().path));
|
||||||
App::Notify("Unstarred "_i18n + m_entries[m_index].GetName());
|
App::Notify("Unstarred "_i18n + GetEntry().GetName());
|
||||||
SortAndFindLastFile();
|
SortAndFindLastFile();
|
||||||
}});
|
}});
|
||||||
} else {
|
} else {
|
||||||
SetAction(Button::R3, Action{"Star"_i18n, [this](){
|
SetAction(Button::R3, Action{"Star"_i18n, [this](){
|
||||||
fs::FsNativeSd().CreateFile(GenerateStarPath(m_entries[m_index].path));
|
fs::FsNativeSd().CreateFile(GenerateStarPath(GetEntry().path));
|
||||||
App::Notify("Starred "_i18n + m_entries[m_index].GetName());
|
App::Notify("Starred "_i18n + GetEntry().GetName());
|
||||||
SortAndFindLastFile();
|
SortAndFindLastFile();
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
@@ -263,19 +181,19 @@ void Menu::SetIndex(s64 index) {
|
|||||||
// todo: fix GetFileTimeStampRaw being different to timeGetCurrentTime
|
// todo: fix GetFileTimeStampRaw being different to timeGetCurrentTime
|
||||||
// log_write("name: %s hbini.ts: %lu file.ts: %lu smaller: %s\n", e.GetName(), e.hbini.timestamp, e.timestamp.modified, e.hbini.timestamp < e.timestamp.modified ? "true" : "false");
|
// log_write("name: %s hbini.ts: %lu file.ts: %lu smaller: %s\n", e.GetName(), e.hbini.timestamp, e.timestamp.modified, e.hbini.timestamp < e.timestamp.modified ? "true" : "false");
|
||||||
|
|
||||||
SetTitleSubHeading(m_entries[m_index].path);
|
SetTitleSubHeading(GetEntry().path);
|
||||||
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries.size()));
|
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries_current.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::InstallHomebrew() {
|
void Menu::InstallHomebrew() {
|
||||||
const auto& nro = m_entries[m_index];
|
const auto& nro = GetEntry();
|
||||||
InstallHomebrew(nro.path, nro_get_icon(nro.path, nro.icon_size, nro.icon_offset));
|
InstallHomebrew(nro.path, nro_get_icon(nro.path, nro.icon_size, nro.icon_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::ScanHomebrew() {
|
void Menu::ScanHomebrew() {
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
FreeEntries();
|
FreeEntries();
|
||||||
nro_scan("/switch", m_entries, m_hide_sphaira.Get());
|
nro_scan("/switch", m_entries);
|
||||||
log_write("nros found: %zu time_taken: %.2f\n", m_entries.size(), ts.GetSecondsD());
|
log_write("nros found: %zu time_taken: %.2f\n", m_entries.size(), ts.GetSecondsD());
|
||||||
|
|
||||||
struct IniUser {
|
struct IniUser {
|
||||||
@@ -301,7 +219,9 @@ void Menu::ScanHomebrew() {
|
|||||||
|
|
||||||
if (user->ini) {
|
if (user->ini) {
|
||||||
if (!strcmp(Key, "timestamp")) {
|
if (!strcmp(Key, "timestamp")) {
|
||||||
user->ini->timestamp = atoi(Value);
|
user->ini->timestamp = ini_parse_getl(Value, 0);
|
||||||
|
} else if (!strcmp(Key, "hidden")) {
|
||||||
|
user->ini->hidden = ini_parse_getbool(Value, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,6 +229,21 @@ void Menu::ScanHomebrew() {
|
|||||||
return 1;
|
return 1;
|
||||||
}, &ini_user, App::PLAYLOG_PATH);
|
}, &ini_user, App::PLAYLOG_PATH);
|
||||||
|
|
||||||
|
// pre-allocate the max size.
|
||||||
|
for (auto& index : m_entries_index) {
|
||||||
|
index.reserve(m_entries.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < m_entries.size(); i++) {
|
||||||
|
auto& e = m_entries[i];
|
||||||
|
|
||||||
|
m_entries_index[Filter_All].emplace_back(i);
|
||||||
|
|
||||||
|
if (!e.hbini.hidden) {
|
||||||
|
m_entries_index[Filter_HideHidden].emplace_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this->Sort();
|
this->Sort();
|
||||||
SetIndex(0);
|
SetIndex(0);
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
@@ -327,7 +262,10 @@ void Menu::Sort() {
|
|||||||
const auto sort = m_sort.Get();
|
const auto sort = m_sort.Get();
|
||||||
const auto order = m_order.Get();
|
const auto order = m_order.Get();
|
||||||
|
|
||||||
const auto sorter = [this, sort, order](const NroEntry& lhs, const NroEntry& rhs) -> bool {
|
const auto sorter = [this, sort, order](u32 _lhs, u32 _rhs) -> bool {
|
||||||
|
const auto& lhs = m_entries[_lhs];
|
||||||
|
const auto& rhs = m_entries[_rhs];
|
||||||
|
|
||||||
const auto name_cmp = [order](const NroEntry& lhs, const NroEntry& rhs) -> bool {
|
const auto name_cmp = [order](const NroEntry& lhs, const NroEntry& rhs) -> bool {
|
||||||
auto r = strcasecmp(lhs.GetName(), rhs.GetName());
|
auto r = strcasecmp(lhs.GetName(), rhs.GetName());
|
||||||
if (!r) {
|
if (!r) {
|
||||||
@@ -403,11 +341,18 @@ void Menu::Sort() {
|
|||||||
std::unreachable();
|
std::unreachable();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::sort(m_entries.begin(), m_entries.end(), sorter);
|
if (m_show_hidden.Get()) {
|
||||||
|
m_entries_current = m_entries_index[Filter_All];
|
||||||
|
} else {
|
||||||
|
m_entries_current = m_entries_index[Filter_HideHidden];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(m_entries_current.begin(), m_entries_current.end(), sorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::SortAndFindLastFile(bool scan) {
|
void Menu::SortAndFindLastFile(bool scan) {
|
||||||
const auto path = m_entries[m_index].path;
|
const auto path = GetEntry().path;
|
||||||
|
|
||||||
if (scan) {
|
if (scan) {
|
||||||
ScanHomebrew();
|
ScanHomebrew();
|
||||||
} else {
|
} else {
|
||||||
@@ -416,8 +361,8 @@ void Menu::SortAndFindLastFile(bool scan) {
|
|||||||
SetIndex(0);
|
SetIndex(0);
|
||||||
|
|
||||||
s64 index = -1;
|
s64 index = -1;
|
||||||
for (u64 i = 0; i < m_entries.size(); i++) {
|
for (u64 i = 0; i < m_entries_current.size(); i++) {
|
||||||
if (path == m_entries[i].path) {
|
if (path == GetEntry(i).path) {
|
||||||
index = i;
|
index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -444,6 +389,9 @@ void Menu::FreeEntries() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_entries.clear();
|
m_entries.clear();
|
||||||
|
for (auto& e : m_entries_index) {
|
||||||
|
e.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::OnLayoutChange() {
|
void Menu::OnLayoutChange() {
|
||||||
@@ -463,4 +411,104 @@ Result Menu::InstallHomebrewFromPath(const fs::FsPath& path) {
|
|||||||
return InstallHomebrew(path, nro_get_icon(path));
|
return InstallHomebrew(path, nro_get_icon(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::DisplayOptions() {
|
||||||
|
auto options = std::make_unique<Sidebar>("Homebrew Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
|
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
||||||
|
|
||||||
|
options->Add<SidebarEntryCallback>("Sort By"_i18n, [this](){
|
||||||
|
auto options = std::make_unique<Sidebar>("Sort Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
|
ON_SCOPE_EXIT(App::Push(std::move(options)));
|
||||||
|
|
||||||
|
SidebarEntryArray::Items sort_items;
|
||||||
|
sort_items.push_back("Updated"_i18n);
|
||||||
|
sort_items.push_back("Alphabetical"_i18n);
|
||||||
|
sort_items.push_back("Size"_i18n);
|
||||||
|
sort_items.push_back("Updated (Star)"_i18n);
|
||||||
|
sort_items.push_back("Alphabetical (Star)"_i18n);
|
||||||
|
sort_items.push_back("Size (Star)"_i18n);
|
||||||
|
|
||||||
|
SidebarEntryArray::Items order_items;
|
||||||
|
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<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||||
|
m_sort.Set(index_out);
|
||||||
|
SortAndFindLastFile();
|
||||||
|
}, m_sort.Get());
|
||||||
|
|
||||||
|
options->Add<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
|
||||||
|
m_order.Set(index_out);
|
||||||
|
SortAndFindLastFile();
|
||||||
|
}, m_order.Get(), "Display entries in Ascending or Descending order."_i18n);
|
||||||
|
|
||||||
|
options->Add<SidebarEntryArray>("Layout"_i18n, layout_items, [this](s64& index_out){
|
||||||
|
m_layout.Set(index_out);
|
||||||
|
OnLayoutChange();
|
||||||
|
}, m_layout.Get(), "Change the layout to List, Icon and Grid."_i18n);
|
||||||
|
|
||||||
|
options->Add<SidebarEntryBool>("Show hidden"_i18n, m_show_hidden.Get(), [this](bool& enable){
|
||||||
|
m_show_hidden.Set(enable);
|
||||||
|
SortAndFindLastFile();
|
||||||
|
}, "Shows all hidden homebrew."_i18n);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!m_entries_current.empty()) {
|
||||||
|
#if 0
|
||||||
|
options->Add<SidebarEntryCallback>("Info"_i18n, [this](){
|
||||||
|
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
options->Add<SidebarEntryBool>("Hide"_i18n, GetEntry().hbini.hidden, [this](bool& v_out){
|
||||||
|
ini_putl(GetEntry().path, "hidden", v_out, App::PLAYLOG_PATH);
|
||||||
|
ScanHomebrew();
|
||||||
|
App::PopToMenu();
|
||||||
|
}, "Hides the selected homebrew.\n\n"
|
||||||
|
"To Unhide homebrew, enable \"Show hidden\" in the sort options."_i18n);
|
||||||
|
|
||||||
|
options->Add<SidebarEntryCallback>("Delete"_i18n, [this](){
|
||||||
|
const auto buf = "Are you sure you want to delete "_i18n + GetEntry().path.toString() + "?";
|
||||||
|
App::Push<OptionBox>(
|
||||||
|
buf,
|
||||||
|
"Back"_i18n, "Delete"_i18n, 1, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
if (R_SUCCEEDED(fs::FsNativeSd().DeleteFile(GetEntry().path))) {
|
||||||
|
// todo: remove from list using real index here.
|
||||||
|
FreeEntry(App::GetVg(), GetEntry());
|
||||||
|
ScanHomebrew();
|
||||||
|
// m_entries.erase(m_entries.begin() + m_index);
|
||||||
|
// SetIndex(m_index ? m_index - 1 : 0);
|
||||||
|
App::PopToMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, GetEntry().image
|
||||||
|
);
|
||||||
|
}, "Perminately delete the selected homebrew.\n\n"
|
||||||
|
"Files and folders created by the homebrew will still remain. "
|
||||||
|
"Use the FileBrowser to delete them."_i18n);
|
||||||
|
|
||||||
|
auto forwarder_entry = options->Add<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){
|
||||||
|
if (App::GetInstallPrompt()) {
|
||||||
|
App::Push<OptionBox>(
|
||||||
|
"WARNING: Installing forwarders will lead to a ban!"_i18n,
|
||||||
|
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
InstallHomebrew();
|
||||||
|
}
|
||||||
|
}, GetEntry().image
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
InstallHomebrew();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
forwarder_entry->Depends(App::GetInstallEnable, i18n::get(App::INSTALL_DEPENDS_STR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::homebrew
|
} // namespace sphaira::ui::menu::homebrew
|
||||||
|
|||||||
Reference in New Issue
Block a user