option to install nro from fs, swap LR display, load translations locally, fix scrolling sound, add file name to rename swkdb
- reduce nxlink svcsleep to reduce latency between polling. - translations can now be loaded from /config/sphaira/i18n/name.json, this is to help aid those creating translations. - swap LR position in display. the fix is a hack, but it'll do for now. - sound effects are now consistent throught the app. - renaming a file will now show the current file name in swkbd, makes it easier to rename from config.ini.template -> config.ini - removed some dead code that was unused. - add credits to the readme. - speed up playlog ini parsing by browsing the ini rather that doing a query for each entry.
This commit is contained in:
@@ -51,3 +51,5 @@ see `assets/romfs/assoc/` for more examples of file assoc entries
|
||||
- libpulsar
|
||||
- minIni
|
||||
- gbatemp
|
||||
- hb-appstore
|
||||
- everyone who has contributed to this project!
|
||||
|
||||
@@ -14,20 +14,20 @@ struct Hbini {
|
||||
};
|
||||
|
||||
struct NroEntry {
|
||||
fs::FsPath path;
|
||||
s64 size;
|
||||
NacpStruct nacp;
|
||||
fs::FsPath path{};
|
||||
s64 size{};
|
||||
NacpStruct nacp{};
|
||||
|
||||
std::vector<u8> icon;
|
||||
u64 icon_size;
|
||||
u64 icon_offset;
|
||||
std::vector<u8> icon{};
|
||||
u64 icon_size{};
|
||||
u64 icon_offset{};
|
||||
|
||||
FsTimeStampRaw timestamp;
|
||||
Hbini hbini;
|
||||
FsTimeStampRaw timestamp{};
|
||||
Hbini hbini{};
|
||||
|
||||
int image; // nvg image
|
||||
int x,y,w,h; // image
|
||||
bool is_nacp_valid;
|
||||
int image{}; // nvg image
|
||||
int x,y,w,h{}; // image
|
||||
bool is_nacp_valid{};
|
||||
|
||||
auto GetName() const -> const char* {
|
||||
return nacp.lang[0].name;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace sphaira::swkbd {
|
||||
|
||||
Result ShowText(std::string& out, const char* guide = nullptr, s64 len_min = -1, s64 len_max = -1);
|
||||
Result ShowNumPad(s64& out, const char* guide = nullptr, s64 len_min = -1, s64 len_max = -1);
|
||||
Result ShowText(std::string& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
|
||||
Result ShowNumPad(s64& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
|
||||
|
||||
} // namespace sphaira::swkbd
|
||||
|
||||
@@ -36,6 +36,9 @@ struct Menu final : MenuBase {
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon);
|
||||
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||
|
||||
|
||||
@@ -26,16 +26,6 @@ struct Widget : public Object {
|
||||
return m_focus;
|
||||
}
|
||||
|
||||
// void PushWidget(std::shared_ptr<Widget> widget);
|
||||
// void PopWidget();
|
||||
|
||||
void SetParent(Widget* parent) {
|
||||
m_parent = parent;
|
||||
}
|
||||
auto GetParent() -> Widget* {
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
auto HasAction(Button button) const -> bool;
|
||||
void SetAction(Button button, Action action);
|
||||
void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) {
|
||||
@@ -66,8 +56,6 @@ struct Widget : public Object {
|
||||
using Actions = std::map<Button, Action>;
|
||||
// using Actions = std::unordered_map<Button, Action>;
|
||||
Actions m_actions;
|
||||
Widget* m_parent{};
|
||||
// std::vector<std::shared_ptr<Widget>> widgets;
|
||||
bool m_focus{false};
|
||||
bool m_pop{false};
|
||||
};
|
||||
|
||||
@@ -524,7 +524,7 @@ void App::Poll() {
|
||||
m_touch_info.finger_id = touch_state.touches[0].finger_id;
|
||||
m_touch_info.is_touching = true;
|
||||
m_touch_info.is_tap = true;
|
||||
PlaySoundEffect(SoundEffect_Limit);
|
||||
// PlaySoundEffect(SoundEffect_Limit);
|
||||
} else if (touch_state.count >= 1 && m_touch_info.is_touching && m_touch_info.finger_id == touch_state.touches[0].finger_id) {
|
||||
m_touch_info.prev_x = m_touch_info.cur_x;
|
||||
m_touch_info.prev_y = m_touch_info.cur_y;
|
||||
@@ -597,39 +597,6 @@ auto App::GetVg() -> NVGcontext* {
|
||||
return g_app->vg;
|
||||
}
|
||||
|
||||
#if 0
|
||||
void App::UpdateList() {
|
||||
const auto index_copy = this->index;
|
||||
const auto start_copy = this->start;
|
||||
|
||||
else if (controller.down) {
|
||||
// todo: replace with actual focus
|
||||
PlaySoundEffect(SoundEffect_Limit);
|
||||
}
|
||||
|
||||
if (controller.any_direction()) {
|
||||
if (index_copy != this->index) {
|
||||
PlaySoundEffect(SoundEffect_Focus);
|
||||
if (start_copy != this->start) {
|
||||
// float r = randomGet64() % 100;
|
||||
// float pitch = r / 100.0;
|
||||
// plsrPlayerSetPitch(m_sound_ids[SoundEffect_Scroll], pitch);
|
||||
PlaySoundEffect(SoundEffect_Scroll);
|
||||
}
|
||||
|
||||
if (this->index == 0 || this->index == this->nro_entries.size() || ((controller.down & HidNpadButton_AnyLeft) && this->index && this->index % 3 == 0) || ((controller.down & HidNpadButton_AnyRight) && this->index && (this->index + 1) % 3 == 0)) {
|
||||
PlaySoundEffect(SoundEffect_Limit);
|
||||
}
|
||||
} else {
|
||||
const auto mask = HidNpadButton_AnyDown | HidNpadButton_AnyUp | HidNpadButton_AnyLeft | HidNpadButton_AnyRight;
|
||||
if (controller.down & mask) {
|
||||
PlaySoundEffect(SoundEffect_Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void DrawElement(float x, float y, float w, float h, ThemeEntryID id) {
|
||||
const auto& e = g_app->m_theme.elements[id];
|
||||
|
||||
|
||||
@@ -78,8 +78,18 @@ bool init(long index) {
|
||||
default: lang_name = "en"; break;
|
||||
}
|
||||
|
||||
const fs::FsPath path = "romfs:/i18n/" + lang_name + ".json";
|
||||
if (R_SUCCEEDED(fs::FsStdio().read_entire_file(path, g_i18n_data))) {
|
||||
const fs::FsPath sdmc_path = "/config/sphaira/i18n/" + lang_name + ".json";
|
||||
const fs::FsPath romfs_path = "romfs:/i18n/" + lang_name + ".json";
|
||||
fs::FsPath path = sdmc_path;
|
||||
|
||||
// try and load override translation first
|
||||
Result rc = fs::FsNativeSd().read_entire_file(path, g_i18n_data);
|
||||
if (R_FAILED(rc)) {
|
||||
path = romfs_path;
|
||||
rc = fs::FsStdio().read_entire_file(path, g_i18n_data);
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
json = yyjson_read((const char*)g_i18n_data.data(), g_i18n_data.size(), YYJSON_READ_ALLOW_TRAILING_COMMAS|YYJSON_READ_ALLOW_COMMENTS|YYJSON_READ_ALLOW_INVALID_UNICODE);
|
||||
if (json) {
|
||||
root = yyjson_doc_get_root(json);
|
||||
|
||||
@@ -71,7 +71,6 @@ auto nro_parse_internal(fs::FsNative& fs, const fs::FsPath& path, NroEntry& entr
|
||||
} else {
|
||||
R_TRY(fsFileRead(&f, data.header.size + asset.nacp.offset, &entry.nacp, sizeof(entry.nacp), FsReadOption_None, &bytes_read));
|
||||
entry.is_nacp_valid = true;
|
||||
log_write("got nacp\n");
|
||||
}
|
||||
|
||||
// lazy load the icons
|
||||
@@ -241,7 +240,7 @@ auto nro_get_icon(const fs::FsPath& path) -> std::vector<u8> {
|
||||
R_TRY_RESULT(fsFileRead(&f, data.header.size, &asset, sizeof(asset), FsReadOption_None, &bytes_read), {});
|
||||
R_UNLESS(asset.magic == NROASSETHEADER_MAGIC, {});
|
||||
|
||||
return nro_get_icon_internal(&f, asset.icon.size, asset.icon.offset);
|
||||
return nro_get_icon_internal(&f, asset.icon.size, data.header.size + asset.icon.offset);
|
||||
}
|
||||
|
||||
auto nro_get_nacp(const fs::FsPath& path, NacpStruct& nacp) -> Result {
|
||||
|
||||
@@ -224,7 +224,7 @@ void loop(void* args) {
|
||||
};
|
||||
|
||||
while (!g_quit) {
|
||||
svcSleepThread(33'333'333);
|
||||
svcSleepThread(1000000);
|
||||
|
||||
if (poll_network_change()) {
|
||||
continue;
|
||||
@@ -267,7 +267,7 @@ void loop(void* args) {
|
||||
sockaddr_in sa_remote{};
|
||||
|
||||
while (!g_quit) {
|
||||
svcSleepThread(33'333'333);
|
||||
svcSleepThread(10000);
|
||||
|
||||
if (poll_network_change()) {
|
||||
break;
|
||||
@@ -297,7 +297,7 @@ void loop(void* args) {
|
||||
}
|
||||
|
||||
fs::FsPath name{};
|
||||
if (namelen > sizeof(name)) {
|
||||
if (namelen >= sizeof(name)) {
|
||||
log_write("namelen is bigger than name: 0x%X\n", socketGetLastResult());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ struct Config {
|
||||
bool numpad{};
|
||||
};
|
||||
|
||||
Result ShowInternal(Config& cfg, const char* guide, s64 len_min, s64 len_max) {
|
||||
Result ShowInternal(Config& cfg, const char* guide, const char* initial, s64 len_min, s64 len_max) {
|
||||
SwkbdConfig c;
|
||||
R_TRY(swkbdCreate(&c, 0));
|
||||
swkbdConfigMakePresetDefault(&c);
|
||||
@@ -24,6 +24,10 @@ Result ShowInternal(Config& cfg, const char* guide, s64 len_min, s64 len_max) {
|
||||
swkbdConfigSetGuideText(&c, guide);
|
||||
}
|
||||
|
||||
if (initial) {
|
||||
swkbdConfigSetInitialText(&c, initial);
|
||||
}
|
||||
|
||||
if (len_min >= 0) {
|
||||
swkbdConfigSetStringLenMin(&c, len_min);
|
||||
}
|
||||
@@ -37,16 +41,16 @@ Result ShowInternal(Config& cfg, const char* guide, s64 len_min, s64 len_max) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Result ShowText(std::string& out, const char* guide, s64 len_min, s64 len_max) {
|
||||
Result ShowText(std::string& out, const char* guide, const char* initial, s64 len_min, s64 len_max) {
|
||||
Config cfg;
|
||||
R_TRY(ShowInternal(cfg, guide, len_min, len_max));
|
||||
R_TRY(ShowInternal(cfg, guide, initial, len_min, len_max));
|
||||
out = cfg.out_text;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ShowNumPad(s64& out, const char* guide, s64 len_min, s64 len_max) {
|
||||
Result ShowNumPad(s64& out, const char* guide, const char* initial, s64 len_min, s64 len_max) {
|
||||
Config cfg;
|
||||
R_TRY(ShowInternal(cfg, guide, len_min, len_max));
|
||||
R_TRY(ShowInternal(cfg, guide, initial, len_min, len_max));
|
||||
out = std::atoll(cfg.out_text);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -661,11 +661,13 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
|
||||
std::make_pair(Button::DPAD_DOWN | Button::RS_DOWN, Action{[this](){
|
||||
if (m_index < (m_options.size() - 1)) {
|
||||
SetIndex(m_index + 1);
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::DPAD_UP | Button::RS_UP, Action{[this](){
|
||||
if (m_index != 0) {
|
||||
SetIndex(m_index - 1);
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::X, Action{"Options"_i18n, [this](){
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ui/menus/filebrowser.hpp"
|
||||
#include "ui/menus/homebrew.hpp"
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
@@ -303,8 +304,6 @@ auto get_collection(fs::FsNative& fs, const fs::FsPath& path, const fs::FsPath&
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
#if 1
|
||||
// recursion
|
||||
auto get_collections(fs::FsNative& fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out) -> Result {
|
||||
// get a list of all the files / dirs
|
||||
FsDirCollection collection;
|
||||
@@ -323,48 +322,6 @@ auto get_collections(fs::FsNative& fs, const fs::FsPath& path, const fs::FsPath&
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
#else
|
||||
// normal
|
||||
auto get_collections(fs::FsNative& fs, fs::FsPath path, fs::FsPath parent_name, FsDirCollections& out) -> Result {
|
||||
// get a list of all the files / dirs
|
||||
struct StoredStack {
|
||||
fs::FsPath path;
|
||||
fs::FsPath parent_name;
|
||||
s64 index;
|
||||
};
|
||||
|
||||
std::stack<StoredStack> stack;
|
||||
s64 index{};
|
||||
// std::vector<StoredStack> indexes;
|
||||
|
||||
while (true) {
|
||||
FsDirCollection collection;
|
||||
R_TRY(get_collection(fs, path, parent_name, collection, true, true, false));
|
||||
out.emplace_back(collection);
|
||||
|
||||
if (collection.dirs.size()) {
|
||||
stack.emplace(path, parent_name, index);
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
FsDirCollection collection;
|
||||
R_TRY(get_collection(fs, path, parent_name, collection, true, true, false));
|
||||
log_write("got collection: %s parent_name: %s files: %zu dirs: %zu\n", path, parent_name, collection.files.size(), collection.dirs.size());
|
||||
out.emplace_back(collection);
|
||||
|
||||
// for (size_t i = 0; i < collection.dirs.size(); i++) {
|
||||
for (const auto&p : collection.dirs) {
|
||||
// use heap as to not explode the stack
|
||||
const auto new_path = std::make_unique<fs::FsPath>(Menu::GetNewPath(path, p.name));
|
||||
const auto new_parent_name = std::make_unique<fs::FsPath>(Menu::GetNewPath(parent_name, p.name));
|
||||
log_write("trying to get nested collection: %s parent_name: %s\n", *new_path, *new_parent_name);
|
||||
R_TRY(get_collections(fs, *new_path, *new_parent_name, out));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out) -> Result {
|
||||
fs::FsNativeSd fs;
|
||||
@@ -392,6 +349,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
std::make_pair(Button::DOWN, Action{[this](){
|
||||
if (m_index < (m_entries_current.size() - 1)) {
|
||||
SetIndex(m_index + 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
if (m_index - m_index_offset >= 8) {
|
||||
log_write("moved down\n");
|
||||
m_index_offset++;
|
||||
@@ -401,6 +359,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
std::make_pair(Button::UP, Action{[this](){
|
||||
if (m_index != 0) {
|
||||
SetIndex(m_index - 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
if (m_index < m_index_offset ) {
|
||||
log_write("moved up\n");
|
||||
m_index_offset--;
|
||||
@@ -571,12 +530,13 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
|
||||
// can't rename more than 1 file
|
||||
if (m_entries_current.size() && m_selected_count < 2) {
|
||||
if (m_entries_current.size() && !m_selected_count) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Rename"_i18n, [this](){
|
||||
std::string out;
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set New File Name", -1, FS_MAX_PATH)) && !out.empty()) {
|
||||
const auto& entry = GetEntry();
|
||||
const auto src_path = GetNewPathCurrent();
|
||||
const auto name = entry.GetName();
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set New File Name", name.c_str())) && !out.empty() && out != name) {
|
||||
const auto src_path = GetNewPath(entry);
|
||||
const auto dst_path = GetNewPath(m_path, out);
|
||||
|
||||
Result rc;
|
||||
@@ -603,7 +563,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Create File"_i18n, [this](){
|
||||
std::string out;
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name", -1, FS_MAX_PATH)) && !out.empty()) {
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name")) && !out.empty()) {
|
||||
fs::FsPath full_path;
|
||||
if (out[0] == '/') {
|
||||
full_path = out;
|
||||
@@ -624,7 +584,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Create Folder"_i18n, [this](){
|
||||
std::string out;
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name", -1, FS_MAX_PATH)) && !out.empty()) {
|
||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name")) && !out.empty()) {
|
||||
fs::FsPath full_path;
|
||||
if (out[0] == '/') {
|
||||
full_path = out;
|
||||
@@ -648,18 +608,22 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
|
||||
if (m_entries_current.size()) {
|
||||
if (HasTypeInSelectedEntries(FsDirEntryType_File) && m_selected_count < 2 && !FindFileAssocFor().empty()) {
|
||||
if (HasTypeInSelectedEntries(FsDirEntryType_File) && !m_selected_count && (GetEntry().GetExtension() == "nro" || !FindFileAssocFor().empty())) {
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){;
|
||||
#if 1
|
||||
App::Push(std::make_shared<OptionBox>(
|
||||
"WARNING: Installing forwarders will lead to a ban!"_i18n,
|
||||
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
if (GetEntry().GetExtension() == "nro") {
|
||||
if (R_FAILED(homebrew::Menu::InstallHomebrewFromPath(GetNewPathCurrent()))) {
|
||||
log_write("failed to create forwarder\n");
|
||||
}
|
||||
} else {
|
||||
InstallForwarder();
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
#endif
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -910,25 +874,6 @@ void Menu::InstallForwarder() {
|
||||
title, items, [this, assoc_list](auto op_index){
|
||||
if (op_index) {
|
||||
const auto assoc = assoc_list[*op_index];
|
||||
#if 1
|
||||
#if 0
|
||||
NroEntry nro{};
|
||||
log_write("parsing nro\n");
|
||||
if (R_FAILED(nro_parse(assoc.path, nro))) {
|
||||
log_write("failed nro parse\n");
|
||||
return;
|
||||
}
|
||||
|
||||
OwoConfig config{};
|
||||
config.nro_path = nro.path.toString();
|
||||
// config.args = nro_add_arg_file(GetNewPathCurrent());
|
||||
config.name = nro.nacp.lang[0].name;// + std::string{" | "} + file_name;
|
||||
// config.name = file_name;
|
||||
config.nacp = nro.nacp;
|
||||
// config.icon = GetRomIcon(pbox, file_name, extension, database, nro);
|
||||
config.icon = nro.icon;// GetRomIcon(pbox, file_name, extension, database, nro);
|
||||
App::Install(config);
|
||||
#else
|
||||
log_write("pushing it\n");
|
||||
App::Push(std::make_shared<ProgressBox>("Installing Forwarder", [assoc, this](auto pbox) -> bool {
|
||||
log_write("inside callback\n");
|
||||
@@ -961,36 +906,6 @@ void Menu::InstallForwarder() {
|
||||
|
||||
return R_SUCCEEDED(App::Install(pbox, config));
|
||||
}));
|
||||
#endif
|
||||
#else
|
||||
|
||||
const auto& assoc = assoc_list[*op_index];
|
||||
log_write("doing nro parse\n");
|
||||
|
||||
NroEntry nro{};
|
||||
if (R_SUCCEEDED(nro_parse(assoc.path.c_str(), nro))) {
|
||||
log_write("got nro data\n");
|
||||
std::string file_name = GetEntry().GetInternalName();
|
||||
std::string extension = GetEntry().GetInternalExtension();
|
||||
|
||||
if (auto pos = file_name.find_last_of('.'); pos != std::string::npos) {
|
||||
log_write("got filename\n");
|
||||
file_name = file_name.substr(0, pos);
|
||||
log_write("got filename2: %s\n\n", file_name.c_str());
|
||||
}
|
||||
|
||||
const auto database = GetRomDatabaseFromPath(m_path);
|
||||
|
||||
OwoConfig config{};
|
||||
config.nro_path = assoc.path;
|
||||
config.args = nro_add_arg_file(GetNewPathCurrent());
|
||||
config.name = nro.nacp.lang[0].name + std::string{" | "} + file_name;
|
||||
// config.name = file_name;
|
||||
config.nacp = nro.nacp;
|
||||
config.icon = GetRomIcon(file_name, extension, database, nro);
|
||||
App::Install(config);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
log_write("pressed B to skip launch...\n");
|
||||
}
|
||||
|
||||
@@ -72,11 +72,10 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||
if (m_index < (m_entries.size() - 1)) {
|
||||
if (m_index < (m_entries.size() - 3)) {
|
||||
SetIndex(m_index + 3);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
} else {
|
||||
SetIndex(m_entries.size() - 1);
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
}
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
if (m_index - m_start >= 9) {
|
||||
log_write("moved down\n");
|
||||
m_start += 3;
|
||||
@@ -263,11 +262,7 @@ void Menu::SetIndex(std::size_t index) {
|
||||
|
||||
void Menu::InstallHomebrew() {
|
||||
const auto& nro = m_entries[m_index];
|
||||
OwoConfig config{};
|
||||
config.nro_path = nro.path.toString();
|
||||
config.nacp = nro.nacp;
|
||||
config.icon = nro.icon;
|
||||
App::Install(config);
|
||||
InstallHomebrew(nro.path, nro.nacp, nro.icon);
|
||||
}
|
||||
|
||||
void Menu::ScanHomebrew() {
|
||||
@@ -275,22 +270,11 @@ void Menu::ScanHomebrew() {
|
||||
nro_scan("/switch", m_entries, m_hide_sphaira.Get());
|
||||
log_write("nros found: %zu time_taken: %.2f\n", m_entries.size(), ts.GetSeconds());
|
||||
|
||||
// todo: optimise this. maybe create a file per entry
|
||||
// which would speed up parsing
|
||||
for (auto& e : m_entries) {
|
||||
if (ini_hassection(e.path, App::PLAYLOG_PATH)) {
|
||||
// log_write("has section for: %s\n", e.path);
|
||||
e.hbini.timestamp = ini_getl(e.path, "timestamp", 0, App::PLAYLOG_PATH);
|
||||
}
|
||||
e.image = 0; // images are lazy loaded
|
||||
}
|
||||
|
||||
#if 0
|
||||
struct IniUser {
|
||||
std::vector<NroEntry>& entires;
|
||||
Hbini* ini;
|
||||
std::string last_section;
|
||||
} ini_user { m_entries };
|
||||
} ini_user{ m_entries };
|
||||
|
||||
ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
auto user = static_cast<IniUser*>(UserData);
|
||||
@@ -308,14 +292,16 @@ void Menu::ScanHomebrew() {
|
||||
}
|
||||
|
||||
if (user->ini) {
|
||||
|
||||
if (!strcmp(Key, "timestamp")) {
|
||||
user->ini->timestamp = atoi(Value);
|
||||
} else if (!strcmp(Key, "launch_count")) {
|
||||
user->ini->launch_count = atoi(Value);
|
||||
}
|
||||
}
|
||||
|
||||
// app->
|
||||
log_write("found: %s %s %s\n", Section, Key, Value);
|
||||
// log_write("found: %s %s %s\n", Section, Key, Value);
|
||||
return 1;
|
||||
}, &ini_user, App::PLAYLOG_PATH);
|
||||
#endif
|
||||
|
||||
this->Sort();
|
||||
SetIndex(0);
|
||||
@@ -374,4 +360,19 @@ void Menu::SortAndFindLastFile() {
|
||||
Sort();
|
||||
}
|
||||
|
||||
Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) {
|
||||
OwoConfig config{};
|
||||
config.nro_path = path.toString();
|
||||
config.nacp = nacp;
|
||||
config.icon = icon;
|
||||
return App::Install(config);
|
||||
}
|
||||
|
||||
Result Menu::InstallHomebrewFromPath(const fs::FsPath& path) {
|
||||
NacpStruct nacp;
|
||||
R_TRY(nro_get_nacp(path, nacp))
|
||||
const auto icon = nro_get_icon(path);
|
||||
return InstallHomebrew(path, nacp, icon);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::homebrew
|
||||
|
||||
@@ -508,7 +508,7 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryCallback>("Page"_i18n, [this](){
|
||||
s64 out;
|
||||
if (R_SUCCEEDED(swkbd::ShowNumPad(out, "Enter Page Number", -1, 3))) {
|
||||
if (R_SUCCEEDED(swkbd::ShowNumPad(out, "Enter Page Number", nullptr, -1, 3))) {
|
||||
if (out < m_page_index_max) {
|
||||
m_page_index = out;
|
||||
PackListDownload();
|
||||
|
||||
@@ -457,7 +457,7 @@ void drawButton(NVGcontext* vg, float x, float y, float size, Button button) {
|
||||
drawText(vg, x, y, size, getButton(button), nullptr, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, getColour(Colour::WHITE));
|
||||
}
|
||||
|
||||
void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor& c, float start_x) {
|
||||
void drawButtons(NVGcontext* vg, const Widget::Actions& _actions, const NVGcolor& c, float start_x) {
|
||||
nvgBeginPath(vg);
|
||||
nvgFontSize(vg, 24.f);
|
||||
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
|
||||
@@ -467,6 +467,22 @@ void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor&
|
||||
const float y = 675.f;
|
||||
float bounds[4]{};
|
||||
|
||||
// swaps L/R position, idc how shit this is, it's called once per frame.
|
||||
std::vector<std::pair<Button, Action>> actions;
|
||||
actions.reserve(_actions.size());
|
||||
|
||||
for (const auto a: _actions) {
|
||||
// swap
|
||||
if (a.first == Button::R && actions.size() && actions.back().first == Button::L) {
|
||||
const auto s = actions.back();
|
||||
actions.back() = a;
|
||||
actions.emplace_back(s);
|
||||
|
||||
} else {
|
||||
actions.emplace_back(a);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [button, action] : actions) {
|
||||
if (action.IsHidden() || action.m_hint.empty()) {
|
||||
continue;
|
||||
|
||||
@@ -123,6 +123,7 @@ auto OptionBox::Setup(std::size_t index) -> void {
|
||||
m_entries[m_index].Selected(false);
|
||||
m_index--;
|
||||
m_entries[m_index].Selected(true);
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::RIGHT, Action{[this](){
|
||||
@@ -130,6 +131,7 @@ auto OptionBox::Setup(std::size_t index) -> void {
|
||||
m_entries[m_index].Selected(false);
|
||||
m_index++;
|
||||
m_entries[m_index].Selected(true);
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
}
|
||||
}}),
|
||||
std::make_pair(Button::A, Action{[this](){
|
||||
|
||||
@@ -90,6 +90,8 @@ auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old_index = m_index;
|
||||
|
||||
if (controller->GotDown(Button::DOWN) && m_index < (m_items.size() - 1)) {
|
||||
m_index++;
|
||||
m_selected_y += m_block.h;
|
||||
@@ -98,7 +100,10 @@ auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
m_selected_y -= m_block.h;
|
||||
}
|
||||
|
||||
if (old_index != m_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
OnLayoutChange();
|
||||
}
|
||||
}
|
||||
|
||||
auto PopupList::OnLayoutChange() -> void {
|
||||
|
||||
@@ -27,6 +27,7 @@ ScrollableText::ScrollableText(const std::string& text, float x, float y, float
|
||||
}
|
||||
m_y_off -= m_step;
|
||||
m_index++;
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
}}),
|
||||
std::make_pair(Button::LS_UP, Action{[this](){
|
||||
if (m_y_off == m_y_off_base) {
|
||||
@@ -34,6 +35,7 @@ ScrollableText::ScrollableText(const std::string& text, float x, float y, float
|
||||
}
|
||||
m_y_off += m_step;
|
||||
m_index--;
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
}})
|
||||
);
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||
|
||||
// if we moved
|
||||
if (m_index != old_index) {
|
||||
App::PlaySoundEffect(SoundEffect_Scroll);
|
||||
m_items[old_index]->OnFocusLost();
|
||||
m_items[m_index]->OnFocusGained();
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace sphaira::ui {
|
||||
void Widget::Update(Controller* controller, TouchInfo* touch) {
|
||||
for (const auto& [button, action] : m_actions) {
|
||||
if ((action.m_type & ActionType::DOWN) && controller->GotDown(button)) {
|
||||
action.Invoke(true);
|
||||
if (static_cast<u64>(button) & static_cast<u64>(Button::ANY_BUTTON)) {
|
||||
App::PlaySoundEffect(SoundEffect_Focus);
|
||||
}
|
||||
action.Invoke(true);
|
||||
}
|
||||
else if ((action.m_type & ActionType::UP) && controller->GotUp(button)) {
|
||||
action.Invoke(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user