From 55c952a51fcef53ff62822d26eaec1e9affbe759 Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Sat, 21 Dec 2024 16:30:32 +0000 Subject: [PATCH] add stars in homebrew menu (hbmenu feature) fixes #22 --- CMakeLists.txt | 7 ++ sphaira/include/nro.hpp | 2 + sphaira/include/ui/menus/homebrew.hpp | 15 +++- sphaira/source/ui/menus/homebrew.cpp | 111 ++++++++++++++++++++++---- 4 files changed, 117 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7622d68..2446358 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,5 +42,12 @@ function(dkp_fatal_if_not_found var package) endif() endfunction(dkp_fatal_if_not_found var package) +# disable exceptions and rtti in order to shrink final binary size. +add_compile_options( + "$<$:-fno-exceptions>" + "$<$:-fno-exceptions>" + "$<$:-fno-rtti>" +) + add_subdirectory(hbl) add_subdirectory(sphaira) diff --git a/sphaira/include/nro.hpp b/sphaira/include/nro.hpp index d7742ab..717fd1c 100644 --- a/sphaira/include/nro.hpp +++ b/sphaira/include/nro.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "fs.hpp" namespace sphaira { @@ -28,6 +29,7 @@ struct NroEntry { int image{}; // nvg image int x,y,w,h{}; // image bool is_nacp_valid{}; + std::optional has_star{std::nullopt}; auto GetName() const -> const char* { return nacp.lang[0].name; diff --git a/sphaira/include/ui/menus/homebrew.hpp b/sphaira/include/ui/menus/homebrew.hpp index d385c30..4d71126 100644 --- a/sphaira/include/ui/menus/homebrew.hpp +++ b/sphaira/include/ui/menus/homebrew.hpp @@ -9,8 +9,11 @@ namespace sphaira::ui::menu::homebrew { enum SortType { SortType_Updated, - SortType_Size, SortType_Alphabetical, + SortType_Size, + SortType_UpdatedStar, + SortType_AlphabeticalStar, + SortType_SizeStar, }; enum OrderType { @@ -36,6 +39,10 @@ struct Menu final : MenuBase { return m_entries; } + auto IsStarEnabled() -> bool { + return m_sort.Get() >= SortType_UpdatedStar; + } + static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector& icon); static Result InstallHomebrewFromPath(const fs::FsPath& path); @@ -46,9 +53,9 @@ private: std::size_t m_start{}; std::size_t m_index{}; // where i am in the array - option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated}; + option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending}; - option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};} -; + option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false}; +}; } // namespace sphaira::ui::menu::homebrew diff --git a/sphaira/source/ui/menus/homebrew.cpp b/sphaira/source/ui/menus/homebrew.cpp index 2bd4cdd..aba493a 100644 --- a/sphaira/source/ui/menus/homebrew.cpp +++ b/sphaira/source/ui/menus/homebrew.cpp @@ -17,6 +17,12 @@ namespace sphaira::ui::menu::homebrew { namespace { +auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath { + fs::FsPath out{}; + const auto dilem = std::strrchr(nro_path.s, '/'); + std::snprintf(out, sizeof(out), "%.*s.%s.star", dilem - nro_path.s + 1, nro_path.s, dilem + 1); + return out; +} } // namespace @@ -74,8 +80,11 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} { SidebarEntryArray::Items sort_items; sort_items.push_back("Updated"_i18n); - sort_items.push_back("Size"_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("Decending"_i18n); @@ -90,6 +99,10 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} { m_order.Set(index_out); SortAndFindLastFile(); }, m_order.Get())); + + options->Add(std::make_shared("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){ + m_hide_sphaira.Set(enable); + }, "Enabled"_i18n, "Disabled"_i18n)); })); #if 0 @@ -113,10 +126,6 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} { )); }, true)); - options->Add(std::make_shared("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){ - m_hide_sphaira.Set(enable); - }, "Enabled"_i18n, "Disabled"_i18n)); - options->Add(std::make_shared("Install Forwarder"_i18n, [this](){ App::Push(std::make_shared( "WARNING: Installing forwarders will lead to a ban!"_i18n, @@ -151,6 +160,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { const u64 max_entry_display = 9; const u64 nro_total = m_entries.size(); const u64 cursor_pos = m_index; + fs::FsNativeSd fs; // only draw scrollbar if needed if (nro_total > max_entry_display) { @@ -187,18 +197,18 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { nvgSave(vg); nvgScissor(vg, x, y, w - 30.f, h); // clip { + bool has_star = false; + if (IsStarEnabled()) { + if (!e.has_star.has_value()) { + e.has_star = fs.FileExists(GenerateStarPath(e.path)); + } + has_star = e.has_star.value(); + } + const float font_size = 18; - const float diff = 32; - #if 1 - gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.GetName()); + gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s%s", has_star ? "\u2605 " : "", e.GetName()); gfx::drawTextArgs(vg, x + 148, y + 80, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.GetAuthor()); gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.GetDisplayVersion()); - #else - gfx::drawTextArgs(vg, x + 148, y + 35, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.GetName()); - gfx::drawTextArgs(vg, x + 148, y + 35 + (diff * 1), font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.GetAuthor()); - gfx::drawTextArgs(vg, x + 148, y + 35 + (diff * 2), font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.GetDisplayVersion()); - // gfx::drawTextArgs(vg, x + 148, y + 110, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "PlayCount: %u", e.hbini.launch_count); - #endif } nvgRestore(vg); } @@ -219,6 +229,26 @@ void Menu::SetIndex(std::size_t index) { } const auto& e = m_entries[m_index]; + + if (IsStarEnabled()) { + const auto star_path = GenerateStarPath(m_entries[m_index].path); + if (fs::FsNativeSd().FileExists(star_path)) { + SetAction(Button::R3, Action{"Unstar"_i18n, [this](){ + fs::FsNativeSd().DeleteFile(GenerateStarPath(m_entries[m_index].path)); + App::Notify("Unstarred "_i18n + m_entries[m_index].GetName()); + SortAndFindLastFile(); + }}); + } else { + SetAction(Button::R3, Action{"Star"_i18n, [this](){ + fs::FsNativeSd().CreateFile(GenerateStarPath(m_entries[m_index].path)); + App::Notify("Starred "_i18n + m_entries[m_index].GetName()); + SortAndFindLastFile(); + }}); + } + } else { + RemoveAction(Button::R3); + } + // TimeCalendarTime caltime; // timeToCalendarTimeWithMyRule() // todo: fix GetFileTimeStampRaw being different to timeGetCurrentTime @@ -276,12 +306,31 @@ void Menu::ScanHomebrew() { } void Menu::Sort() { + if (IsStarEnabled()) { + fs::FsNativeSd fs; + fs::FsPath star_path; + for (auto& p : m_entries) { + p.has_star = fs.FileExists(GenerateStarPath(p.path)); + if (p.has_star) { + log_write("found star: %s\n", p.path.s); + } else { + log_write("no star: %s\n", p.path.s); + } + } + } + // returns true if lhs should be before rhs const auto sort = m_sort.Get(); const auto order = m_order.Get(); const auto sorter = [this, sort, order](const NroEntry& lhs, const NroEntry& rhs) -> bool { switch (sort) { + case SortType_UpdatedStar: + if (lhs.has_star.value() && !rhs.has_star.value()) { + return true; + } else if (!lhs.has_star.value() && rhs.has_star.value()) { + return false; + } case SortType_Updated: { auto lhs_timestamp = lhs.hbini.timestamp; auto rhs_timestamp = rhs.hbini.timestamp; @@ -300,6 +349,13 @@ void Menu::Sort() { return lhs_timestamp < rhs_timestamp; } } break; + + case SortType_SizeStar: + if (lhs.has_star.value() && !rhs.has_star.value()) { + return true; + } else if (!lhs.has_star.value() && rhs.has_star.value()) { + return false; + } case SortType_Size: { if (lhs.size == rhs.size) { return strcasecmp(lhs.GetName(), rhs.GetName()) < 0; @@ -309,6 +365,13 @@ void Menu::Sort() { return lhs.size < rhs.size; } } break; + + case SortType_AlphabeticalStar: + if (lhs.has_star.value() && !rhs.has_star.value()) { + return true; + } else if (!lhs.has_star.value() && rhs.has_star.value()) { + return false; + } case SortType_Alphabetical: { if (order == OrderType_Decending) { return strcasecmp(lhs.GetName(), rhs.GetName()) < 0; @@ -325,7 +388,27 @@ void Menu::Sort() { } void Menu::SortAndFindLastFile() { + const auto path = m_entries[m_index].path; Sort(); + SetIndex(0); + + s64 index = -1; + for (u64 i = 0; i < m_entries.size(); i++) { + if (path == m_entries[i].path) { + index = i; + break; + } + } + + if (index >= 0) { + // guesstimate where the position is + if (index >= 9) { + m_start = (index - 9) / 3 * 3 + 3; + } else { + m_start = 0; + } + SetIndex(index); + } } Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector& icon) {