add support for changing the left-side menu.

fixes #148
This commit is contained in:
ITotalJustice
2025-05-26 22:55:06 +01:00
parent e2a5454263
commit c11d9edc4e
27 changed files with 164 additions and 63 deletions

View File

@@ -240,7 +240,8 @@ public:
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
option::OptionString m_right_side_menu{INI_SECTION, "right_side_menu", "Appstore"};
option::OptionString m_left_menu{INI_SECTION, "left_side_menu", "FileBrowser"};
option::OptionString m_right_menu{INI_SECTION, "right_side_menu", "Appstore"};
option::OptionBool m_progress_boost_mode{INI_SECTION, "progress_boost_mode", true};
// install options

View File

@@ -139,7 +139,7 @@ enum OrderType {
using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Store"; };

View File

@@ -3,7 +3,6 @@
#include "ui/menus/menu_base.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/list.hpp"
#include "nro.hpp"
#include "fs.hpp"
#include "option.hpp"
#include "hasher.hpp"
@@ -341,7 +340,7 @@ struct SelectedStash {
struct Menu final : MenuBase {
friend class FsView;
Menu(const std::vector<NroEntry>& nro_entries);
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Files"; };
@@ -381,10 +380,11 @@ private:
void UpdateSubheading();
void PromptIfShouldExit();
private:
static constexpr inline const char* INI_SECTION = "filebrowser";
const std::vector<NroEntry>& m_nro_entries;
std::shared_ptr<FsView> view{};
std::shared_ptr<FsView> view_left{};
std::shared_ptr<FsView> view_right{};

View File

@@ -35,7 +35,7 @@ struct StreamFtp final : yati::source::Stream {
};
struct Menu final : MenuBase {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "FTP"; };

View File

@@ -88,7 +88,7 @@ enum OrderType {
using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Games"; };

View File

@@ -166,7 +166,7 @@ struct ApplicationEntry {
};
struct Menu final : MenuBase {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "GC"; };

View File

@@ -42,7 +42,7 @@ struct GhApiEntry {
};
struct Menu final : MenuBase {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "GitHub"; };

View File

@@ -24,6 +24,8 @@ enum OrderType {
using LayoutType = grid::LayoutType;
auto GetNroEntries() -> std::span<const NroEntry>;
struct Menu final : grid::Menu {
Menu();
~Menu();

View File

@@ -27,7 +27,7 @@ struct Entry {
};
struct Menu final : MenuBase {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "IRS"; };

View File

@@ -1,8 +1,8 @@
#pragma once
#include "ui/widget.hpp"
#include "ui/menus/homebrew.hpp"
#include "ui/menus/filebrowser.hpp"
#include "ui/menus/menu_base.hpp"
#include <span>
namespace sphaira::ui::menu::main {
@@ -17,7 +17,7 @@ enum class UpdateState {
Error,
};
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(void)>;
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(u32 flags)>;
enum MiscMenuFlag : u8 {
// can be set as the rightside menu.
@@ -62,9 +62,9 @@ private:
void AddOnLRPress();
private:
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{};
std::shared_ptr<MenuBase> m_right_side_menu{};
std::shared_ptr<MenuBase> m_centre_menu{};
std::shared_ptr<MenuBase> m_left_menu{};
std::shared_ptr<MenuBase> m_right_menu{};
std::shared_ptr<MenuBase> m_current_menu{};
std::string m_update_url{};

View File

@@ -6,6 +6,11 @@
namespace sphaira::ui::menu {
enum MenuFlag {
MenuFlag_None = 0,
MenuFlag_Tab = 1 << 1,
};
struct PolledData {
struct tm tm{};
u32 battery_percetange{};
@@ -17,7 +22,7 @@ struct PolledData {
};
struct MenuBase : Widget {
MenuBase(std::string title);
MenuBase(const std::string& title, u32 flags);
virtual ~MenuBase();
virtual auto GetShortTitle() const -> const char* = 0;
@@ -36,6 +41,10 @@ struct MenuBase : Widget {
return m_title;
}
auto IsTab() const -> bool {
return m_flags & MenuFlag_Tab;
}
static auto GetPolledData(bool force_refresh = false) -> PolledData;
private:
@@ -45,6 +54,8 @@ private:
ScrollingText m_scroll_title_sub_heading{};
ScrollingText m_scroll_sub_heading{};
u32 m_flags{};
};
} // namespace sphaira::ui::menu

View File

@@ -130,7 +130,7 @@ struct PageEntry {
};
struct Menu final : MenuBase {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Themezer"; };

View File

@@ -21,7 +21,7 @@ enum class State {
};
struct Menu final : MenuBase {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "USB"; };

View File

@@ -1310,7 +1310,8 @@ App::App(const char* argv0) {
else if (app->m_theme_music.LoadFrom(Key, Value)) {}
else if (app->m_12hour_time.LoadFrom(Key, Value)) {}
else if (app->m_language.LoadFrom(Key, Value)) {}
else if (app->m_right_side_menu.LoadFrom(Key, Value)) {}
else if (app->m_left_menu.LoadFrom(Key, Value)) {}
else if (app->m_right_menu.LoadFrom(Key, Value)) {}
else if (app->m_install_sysmmc.LoadFrom(Key, Value)) {}
else if (app->m_install_emummc.LoadFrom(Key, Value)) {}
else if (app->m_install_sd.LoadFrom(Key, Value)) {}
@@ -1581,7 +1582,7 @@ void App::DisplayMiscOptions(bool left_side) {
ON_SCOPE_EXIT(App::Push(options));
for (auto& e : ui::menu::main::GetMiscMenuEntries()) {
if (e.name == g_app->m_right_side_menu.Get()) {
if (e.name == g_app->m_right_menu.Get()) {
continue;
}
@@ -1590,7 +1591,7 @@ void App::DisplayMiscOptions(bool left_side) {
}
options->Add(std::make_shared<ui::SidebarEntryCallback>(i18n::get(e.title), [e](){
App::Push(e.func());
App::Push(e.func(ui::menu::MenuFlag_None));
}));
}
}
@@ -1617,14 +1618,14 @@ void App::DisplayAdvancedOptions(bool left_side) {
menu_names.emplace_back(e.name);
}
ui::SidebarEntryArray::Items right_side_menu_items;
ui::SidebarEntryArray::Items side_menu_items;
for (auto& str : menu_names) {
right_side_menu_items.push_back(i18n::get(str));
side_menu_items.push_back(i18n::get(str));
}
const auto it = std::find(menu_names.cbegin(), menu_names.cend(), g_app->m_right_side_menu.Get());
const auto it = std::find(menu_names.cbegin(), menu_names.cend(), g_app->m_right_menu.Get());
if (it == menu_names.cend()) {
g_app->m_right_side_menu.Set(menu_names[0]);
g_app->m_right_menu.Set(menu_names[0]);
}
options->Add(std::make_shared<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
@@ -1643,17 +1644,29 @@ void App::DisplayAdvancedOptions(bool left_side) {
App::SetTextScrollSpeed(index_out);
}, App::GetTextScrollSpeed()));
options->Add(std::make_shared<ui::SidebarEntryArray>("Set right-side menu"_i18n, right_side_menu_items, [menu_names](s64& index_out){
options->Add(std::make_shared<ui::SidebarEntryArray>("Set left-side menu"_i18n, side_menu_items, [menu_names](s64& index_out){
const auto e = menu_names[index_out];
if (g_app->m_right_side_menu.Get() != e) {
g_app->m_right_side_menu.Set(e);
if (g_app->m_left_menu.Get() != e) {
g_app->m_left_menu.Set(e);
App::Push(std::make_shared<ui::OptionBox>(
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
App::ExitRestart();
}
));
}
}, i18n::get(g_app->m_right_side_menu.Get())));
}, i18n::get(g_app->m_left_menu.Get())));
options->Add(std::make_shared<ui::SidebarEntryArray>("Set right-side menu"_i18n, side_menu_items, [menu_names](s64& index_out){
const auto e = menu_names[index_out];
if (g_app->m_right_menu.Get() != e) {
g_app->m_right_menu.Set(e);
App::Push(std::make_shared<ui::OptionBox>(
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
App::ExitRestart();
}
));
}
}, i18n::get(g_app->m_right_menu.Get())));
options->Add(std::make_shared<ui::SidebarEntryCallback>("Install options"_i18n, [left_side](){
App::DisplayInstallOptions(left_side);

View File

@@ -603,7 +603,7 @@ auto FindCaseInsensitive(std::string_view base, std::string_view term) -> bool {
} // namespace
EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
: MenuBase{entry.title}
: MenuBase{entry.title, MenuFlag_None}
, m_entry{entry}
, m_default_icon{default_icon}
, m_menu{menu} {
@@ -852,7 +852,7 @@ void EntryMenu::SetIndex(s64 index) {
}
}
Menu::Menu() : grid::Menu{"AppStore"_i18n} {
Menu::Menu(u32 flags) : grid::Menu{"AppStore"_i18n, flags} {
fs::FsNativeSd fs;
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons");
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");

View File

@@ -6,7 +6,7 @@ namespace {
} // namespace
Menu::Menu(const fs::FsPath& path) : MenuBase{path}, m_path{path} {
Menu::Menu(const fs::FsPath& path) : MenuBase{path, MenuFlag_None}, m_path{path} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});

View File

@@ -382,6 +382,11 @@ FsView::FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSid
}}),
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
if (!m_menu->IsTab() && App::GetApp()->m_controller.GotHeld(Button::R2)) {
m_menu->PromptIfShouldExit();
return;
}
std::string_view view{m_path};
if (view != m_fs->Root()) {
const auto end = view.find_last_of('/');
@@ -392,6 +397,10 @@ FsView::FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSid
} else {
Scan(view.substr(0, end), true);
}
} else {
if (!m_menu->IsTab()) {
m_menu->PromptIfShouldExit();
}
}
}}),
@@ -1390,7 +1399,7 @@ auto FsView::CheckIfUpdateFolder() -> Result {
if (!m_daybreak_path.has_value()) {
auto daybreak_path = DAYBREAK_PATH;
if (!m_fs->FileExists(DAYBREAK_PATH)) {
if (auto e = nro_find(m_menu->m_nro_entries, "Daybreak", "Atmosphere-NX", {}); e.has_value()) {
if (auto e = nro_find(homebrew::GetNroEntries(), "Daybreak", "Atmosphere-NX", {}); e.has_value()) {
daybreak_path = e.value().path;
} else {
log_write("failed to find daybreak\n");
@@ -1893,11 +1902,17 @@ void FsView::DisplayAdvancedOptions() {
}));
}
Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i18n}, m_nro_entries{nro_entries} {
Menu::Menu(u32 flags) : MenuBase{"FileBrowser"_i18n, flags} {
SetAction(Button::L3, Action{"Split"_i18n, [this](){
SetSplitScreen(IsSplitScreen() ^ 1);
}});
if (!IsTab()) {
SetAction(Button::SELECT, Action{"Close"_i18n, [this](){
PromptIfShouldExit();
}});
}
view = view_left = std::make_shared<FsView>(this, ViewSide::Left);
}
@@ -2063,7 +2078,7 @@ void Menu::LoadAssocEntriesPath(const fs::FsPath& path) {
file_exists = view->m_fs->FileExists(assoc.path);
} else {
const auto nro_name = assoc.name + ".nro";
for (const auto& nro : m_nro_entries) {
for (const auto& nro : homebrew::GetNroEntries()) {
const auto len = std::strlen(nro.path);
if (len < nro_name.length()) {
continue;
@@ -2160,4 +2175,19 @@ void Menu::RefreshViews() {
}
}
void Menu::PromptIfShouldExit() {
if (IsTab()) {
return;
}
App::Push(std::make_shared<ui::OptionBox>(
"Close FileBrowser?"_i18n,
"No"_i18n, "Yes"_i18n, 1, [this](auto op_index){
if (op_index && *op_index) {
SetPop();
}
}
));
}
} // namespace sphaira::ui::menu::filebrowser

View File

@@ -142,7 +142,7 @@ void StreamFtp::Disable() {
m_active = false;
}
Menu::Menu() : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n} {
Menu::Menu(u32 flags) : MenuBase{"FTP Install (EXPERIMENTAL)"_i18n, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});

View File

@@ -691,7 +691,7 @@ void ThreadData::Pop(std::vector<ThreadResultData>& out) {
m_result.clear();
}
Menu::Menu() : grid::Menu{"Games"_i18n} {
Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
this->SetActions(
std::make_pair(Button::L3, Action{[this](){
if (m_entries.empty()) {

View File

@@ -390,7 +390,7 @@ auto ApplicationEntry::GetSize() const -> s64 {
return size;
}
Menu::Menu() : MenuBase{"GameCard"_i18n} {
Menu::Menu(u32 flags) : MenuBase{"GameCard"_i18n, flags} {
this->SetActions(
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
SetPop();

View File

@@ -222,7 +222,7 @@ auto DownloadAssetJson(ProgressBox* pbox, const std::string& url, GhApiEntry& ou
} // namespace
Menu::Menu() : MenuBase{"GitHub"_i18n} {
Menu::Menu(u32 flags) : MenuBase{"GitHub"_i18n, flags} {
fs::FsNativeSd().CreateDirectoryRecursively(CACHE_PATH);
this->SetActions(

View File

@@ -18,6 +18,8 @@
namespace sphaira::ui::menu::homebrew {
namespace {
Menu* g_menu{};
auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath {
fs::FsPath out{};
const auto dilem = std::strrchr(nro_path.s, '/');
@@ -32,7 +34,17 @@ void FreeEntry(NVGcontext* vg, NroEntry& e) {
} // namespace
Menu::Menu() : grid::Menu{"Homebrew"_i18n} {
auto GetNroEntries() -> std::span<const NroEntry> {
if (!g_menu) {
return {};
}
return g_menu->GetHomebrewList();
}
Menu::Menu() : grid::Menu{"Homebrew"_i18n, MenuFlag_Tab} {
g_menu = this;
this->SetActions(
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
nro_launch(m_entries[m_index].path);
@@ -129,6 +141,7 @@ Menu::Menu() : grid::Menu{"Homebrew"_i18n} {
}
Menu::~Menu() {
g_menu = {};
FreeEntries();
}

View File

@@ -75,7 +75,7 @@ void irsConvertConfigNormalToEx(const IrsImageTransferProcessorConfig* nor, IrsI
} // namespace
Menu::Menu() : MenuBase{"Irs"_i18n} {
Menu::Menu(u32 flags) : MenuBase{"Irs"_i18n, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});

View File

@@ -6,6 +6,8 @@
#include "ui/progress_box.hpp"
#include "ui/error_box.hpp"
#include "ui/menus/homebrew.hpp"
#include "ui/menus/filebrowser.hpp"
#include "ui/menus/irs_menu.hpp"
#include "ui/menus/themezer.hpp"
#include "ui/menus/ghdl.hpp"
@@ -32,13 +34,14 @@ constexpr const char* GITHUB_URL{"https://api.github.com/repos/ITotalJustice/sph
constexpr fs::FsPath CACHE_PATH{"/switch/sphaira/cache/sphaira_latest.json"};
template<typename T>
auto MiscMenuFuncGenerator() {
return std::make_shared<T>();
auto MiscMenuFuncGenerator(u32 flags) {
return std::make_shared<T>(flags);
}
const MiscMenuEntry MISC_MENU_ENTRIES[] = {
{ .name = "Appstore", .title = "Appstore", .func = MiscMenuFuncGenerator<ui::menu::appstore::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "Games", .title = "Games", .func = MiscMenuFuncGenerator<ui::menu::game::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "FileBrowser", .title = "FileBrowser", .func = MiscMenuFuncGenerator<ui::menu::filebrowser::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "Themezer", .title = "Themezer", .func = MiscMenuFuncGenerator<ui::menu::themezer::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "GitHub", .title = "GitHub", .func = MiscMenuFuncGenerator<ui::menu::gh::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "FTP", .title = "FTP Install", .func = MiscMenuFuncGenerator<ui::menu::ftp::Menu>, .flag = MiscMenuFlag_Install },
@@ -149,16 +152,41 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
R_SUCCEED();
}
auto CreateRightSideMenu() -> std::shared_ptr<MenuBase> {
const auto name = App::GetApp()->m_right_side_menu.Get();
auto CreateLeftSideMenu(std::string& name_out) -> std::shared_ptr<MenuBase> {
const auto name = App::GetApp()->m_left_menu.Get();
for (auto& e : GetMiscMenuEntries()) {
if (e.name == name) {
return e.func();
name_out = name;
return e.func(MenuFlag_Tab);
}
}
return std::make_shared<ui::menu::appstore::Menu>();
name_out = "FileBrowser";
return std::make_shared<ui::menu::filebrowser::Menu>(MenuFlag_Tab);
}
auto CreateRightSideMenu(std::string_view left_name) -> std::shared_ptr<MenuBase> {
const auto name = App::GetApp()->m_right_menu.Get();
// handle if the user tries to mount the same menu twice.
if (name == left_name) {
// check if we can mount the default.
if (left_name != "AppStore") {
return std::make_shared<ui::menu::appstore::Menu>(MenuFlag_Tab);
} else {
// otherwise, fallback to left side default.
return std::make_shared<ui::menu::filebrowser::Menu>(MenuFlag_Tab);
}
}
for (auto& e : GetMiscMenuEntries()) {
if (e.name == name) {
return e.func(MenuFlag_Tab);
}
}
return std::make_shared<ui::menu::appstore::Menu>(MenuFlag_Tab);
}
} // namespace
@@ -319,10 +347,13 @@ MainMenu::MainMenu() {
}})
);
m_homebrew_menu = std::make_shared<homebrew::Menu>();
m_filebrowser_menu = std::make_shared<filebrowser::Menu>(m_homebrew_menu->GetHomebrewList());
m_right_side_menu = CreateRightSideMenu();
m_current_menu = m_homebrew_menu;
m_centre_menu = std::make_shared<homebrew::Menu>();
m_current_menu = m_centre_menu;
std::string left_side_name;
m_left_menu = CreateLeftSideMenu(left_side_name);
m_right_menu = CreateRightSideMenu(left_side_name);
AddOnLRPress();
@@ -355,11 +386,11 @@ void MainMenu::OnFocusLost() {
void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
m_current_menu->OnFocusLost();
if (m_current_menu == m_homebrew_menu) {
if (m_current_menu == m_centre_menu) {
m_current_menu = menu;
RemoveAction(b);
} else {
m_current_menu = m_homebrew_menu;
m_current_menu = m_centre_menu;
}
AddOnLRPress();
@@ -371,17 +402,17 @@ void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
}
void MainMenu::AddOnLRPress() {
if (m_current_menu != m_filebrowser_menu) {
const auto label = m_current_menu == m_homebrew_menu ? m_filebrowser_menu->GetShortTitle() : m_homebrew_menu->GetShortTitle();
if (m_current_menu != m_left_menu) {
const auto label = m_current_menu == m_centre_menu ? m_left_menu->GetShortTitle() : m_centre_menu->GetShortTitle();
SetAction(Button::L, Action{i18n::get(label), [this]{
OnLRPress(m_filebrowser_menu, Button::L);
OnLRPress(m_left_menu, Button::L);
}});
}
if (m_current_menu != m_right_side_menu) {
const auto label = m_current_menu == m_homebrew_menu ? m_right_side_menu->GetShortTitle() : m_homebrew_menu->GetShortTitle();
if (m_current_menu != m_right_menu) {
const auto label = m_current_menu == m_centre_menu ? m_right_menu->GetShortTitle() : m_centre_menu->GetShortTitle();
SetAction(Button::R, Action{i18n::get(label), [this]{
OnLRPress(m_right_side_menu, Button::R);
OnLRPress(m_right_menu, Button::R);
}});
}
}

View File

@@ -40,7 +40,7 @@ auto MenuBase::GetPolledData(bool force_refresh) -> PolledData {
return data;
}
MenuBase::MenuBase(std::string title) : m_title{title} {
MenuBase::MenuBase(const std::string& title, u32 flags) : m_title{title}, m_flags{flags} {
// this->SetParent(this);
this->SetPos(30, 87, 1220 - 30, 646 - 87);
SetAction(Button::START, Action{App::Exit});

View File

@@ -346,7 +346,7 @@ LazyImage::~LazyImage() {
}
}
Menu::Menu() : MenuBase{"Themezer"_i18n} {
Menu::Menu(u32 flags) : MenuBase{"Themezer"_i18n, flags} {
fs::FsNativeSd().CreateDirectoryRecursively(CACHE_PATH);
SetAction(Button::B, Action{"Back"_i18n, [this]{

View File

@@ -51,7 +51,7 @@ void thread_func(void* user) {
} // namespace
Menu::Menu() : MenuBase{"USB"_i18n} {
Menu::Menu(u32 flags) : MenuBase{"USB"_i18n, flags} {
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});