diff --git a/include/gamecard_tab.hpp b/include/gamecard_tab.hpp index 8602ab1..e09ff86 100644 --- a/include/gamecard_tab.hpp +++ b/include/gamecard_tab.hpp @@ -32,7 +32,7 @@ namespace nxdt::views { class GameCardTab: public LayeredErrorFrame { - typedef bool (*GameCardSizeFunc)(u64 *size); + typedef bool (*GameCardSizeFunc)(u64 *out_size); private: nxdt::tasks::GameCardTask *gc_status_task = nullptr; diff --git a/include/layered_error_frame.hpp b/include/layered_error_frame.hpp index bc480dc..f8f1f1a 100644 --- a/include/layered_error_frame.hpp +++ b/include/layered_error_frame.hpp @@ -31,22 +31,14 @@ namespace nxdt::views /* Extended class to switch between ErrorFrame and List views on demand. */ class LayeredErrorFrame: public brls::LayerView { - private: - int layer_view_index = 0; - - ErrorFrame *error_frame = nullptr; - - brls::List *list = nullptr; - std::vector list_views; - protected: + ErrorFrame *error_frame = nullptr; + brls::List *list = nullptr; + void SwitchLayerView(bool use_error_frame); - void SetErrorFrameMessage(std::string msg = ""); - void AddListView(brls::View* view); public: - LayeredErrorFrame(void); - ~LayeredErrorFrame(void); + LayeredErrorFrame(std::string msg = ""); }; } diff --git a/include/user_titles_tab.hpp b/include/user_titles_tab.hpp new file mode 100644 index 0000000..00b8c28 --- /dev/null +++ b/include/user_titles_tab.hpp @@ -0,0 +1,47 @@ +/* + * user_titles_tab.hpp + * + * Copyright (c) 2020-2021, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#ifndef __USER_TITLES_TAB_HPP__ +#define __USER_TITLES_TAB_HPP__ + +#include "tasks.hpp" +#include "layered_error_frame.hpp" + +namespace nxdt::views +{ + class UserTitlesTab: public LayeredErrorFrame + { + private: + nxdt::tasks::TitleTask *title_task = nullptr; + nxdt::tasks::VoidEvent::Subscription title_task_sub; + nxdt::tasks::TitleApplicationMetadataVector *user_app_metadata = nullptr; + + void PopulateList(void); + + public: + UserTitlesTab(nxdt::tasks::TitleTask *title_task); + ~UserTitlesTab(void); + }; +} + +#endif /* __USER_TITLES_TAB_HPP__ */ diff --git a/romfs/i18n/en-US/user_titles_tab.json b/romfs/i18n/en-US/user_titles_tab.json new file mode 100644 index 0000000..70bfe9d --- /dev/null +++ b/romfs/i18n/en-US/user_titles_tab.json @@ -0,0 +1,5 @@ +{ + "error_frame": { + "no_titles_available": "No user titles available." + } +} diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index ebdd03e..bb6f2f4 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -41,13 +41,14 @@ namespace nxdt::views [GameCardCompatibilityType_Terra] = "Terra" }; - GameCardTab::GameCardTab(nxdt::tasks::GameCardTask *gc_status_task) : LayeredErrorFrame(), gc_status_task(gc_status_task) + GameCardTab::GameCardTab(nxdt::tasks::GameCardTask *gc_status_task) : LayeredErrorFrame("gamecard_tab/error_frame/not_inserted"_i18n), gc_status_task(gc_status_task) { - /* Error frame. */ - this->SetErrorFrameMessage("gamecard_tab/error_frame/not_inserted"_i18n); + /* Set custom spacing. */ + this->list->setSpacing(this->list->getSpacing() / 2); + this->list->setMarginBottom(20); /* Gamecard properties table. */ - this->AddListView(new brls::Header("gamecard_tab/list/properties_table/header"_i18n)); + this->list->addView(new brls::Header("gamecard_tab/list/properties_table/header"_i18n)); this->properties_table = new FocusableTable(false); this->capacity = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/capacity"_i18n); @@ -57,28 +58,28 @@ namespace nxdt::views this->lafw_version = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/lafw_version"_i18n); this->sdk_version = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/sdk_version"_i18n); this->compatibility_type = this->properties_table->addRow(brls::TableRowType::BODY, "gamecard_tab/list/properties_table/compatibility_type"_i18n); - this->AddListView(this->properties_table); + this->list->addView(this->properties_table); /* ListItem elements. */ - this->AddListView(new brls::Header("gamecard_tab/list/dump_options"_i18n)); + this->list->addView(new brls::Header("gamecard_tab/list/dump_options"_i18n)); this->dump_card_image = new brls::ListItem("gamecard_tab/list/dump_card_image/label"_i18n, "gamecard_tab/list/dump_card_image/description"_i18n); - this->AddListView(this->dump_card_image); + this->list->addView(this->dump_card_image); this->dump_certificate = new brls::ListItem("gamecard_tab/list/dump_certificate/label"_i18n, "gamecard_tab/list/dump_certificate/description"_i18n); - this->AddListView(this->dump_certificate); + this->list->addView(this->dump_certificate); this->dump_header = new brls::ListItem("gamecard_tab/list/dump_header/label"_i18n, "gamecard_tab/list/dump_header/description"_i18n); - this->AddListView(this->dump_header); + this->list->addView(this->dump_header); this->dump_decrypted_cardinfo = new brls::ListItem("gamecard_tab/list/dump_decrypted_cardinfo/label"_i18n, "gamecard_tab/list/dump_decrypted_cardinfo/description"_i18n); - this->AddListView(this->dump_decrypted_cardinfo); + this->list->addView(this->dump_decrypted_cardinfo); this->dump_initial_data = new brls::ListItem("gamecard_tab/list/dump_initial_data/label"_i18n, "gamecard_tab/list/dump_initial_data/description"_i18n); - this->AddListView(this->dump_initial_data); + this->list->addView(this->dump_initial_data); this->dump_hfs_partitions = new brls::ListItem("gamecard_tab/list/dump_hfs_partitions/label"_i18n, "gamecard_tab/list/dump_hfs_partitions/description"_i18n); - this->AddListView(this->dump_hfs_partitions); + this->list->addView(this->dump_hfs_partitions); /* Subscribe to gamecard status event. */ this->gc_status_task_sub = this->gc_status_task->RegisterListener([this](GameCardStatus gc_status) { @@ -87,19 +88,19 @@ namespace nxdt::views switch(gc_status) { case GameCardStatus_NotInserted: - this->SetErrorFrameMessage("gamecard_tab/error_frame/not_inserted"_i18n); + this->error_frame->SetMessage("gamecard_tab/error_frame/not_inserted"_i18n); break; case GameCardStatus_Processing: - this->SetErrorFrameMessage("gamecard_tab/error_frame/processing"_i18n); + this->error_frame->SetMessage("gamecard_tab/error_frame/processing"_i18n); break; case GameCardStatus_NoGameCardPatchEnabled: - this->SetErrorFrameMessage("gamecard_tab/error_frame/nogc_enabled"_i18n); + this->error_frame->SetMessage("gamecard_tab/error_frame/nogc_enabled"_i18n); break; case GameCardStatus_LotusAsicFirmwareUpdateRequired: - this->SetErrorFrameMessage("gamecard_tab/error_frame/lafw_update_required"_i18n); + this->error_frame->SetMessage("gamecard_tab/error_frame/lafw_update_required"_i18n); break; case GameCardStatus_InsertedAndInfoNotLoaded: - this->SetErrorFrameMessage(i18n::getStr("gamecard_tab/error_frame/info_not_loaded"_i18n, GITHUB_NEW_ISSUE_URL)); + this->error_frame->SetMessage(i18n::getStr("gamecard_tab/error_frame/info_not_loaded"_i18n, GITHUB_NEW_ISSUE_URL)); break; case GameCardStatus_InsertedAndInfoLoaded: { diff --git a/source/layered_error_frame.cpp b/source/layered_error_frame.cpp index 018f641..18672aa 100644 --- a/source/layered_error_frame.cpp +++ b/source/layered_error_frame.cpp @@ -23,56 +23,35 @@ namespace nxdt::views { - LayeredErrorFrame::LayeredErrorFrame(void) : brls::LayerView() + LayeredErrorFrame::LayeredErrorFrame(std::string msg) : brls::LayerView() { /* Error frame. */ - this->error_frame = new ErrorFrame(); + this->error_frame = new ErrorFrame(msg); this->addLayer(this->error_frame); /* List. */ this->list = new brls::List(); - this->list->setSpacing(this->list->getSpacing() / 2); - this->list->setMarginBottom(20); this->addLayer(this->list); } - LayeredErrorFrame::~LayeredErrorFrame(void) - { - /* Clear list views vector. */ - if (this->list_views.size()) this->list_views.clear(); - } - void LayeredErrorFrame::SwitchLayerView(bool use_error_frame) { - if ((use_error_frame && this->layer_view_index == 0) || (!use_error_frame && this->layer_view_index == 1)) return; + int index = this->getLayerIndex(); + int new_index = (index ^ 1); + brls::View *cur_focus = brls::Application::getCurrentFocus(); - int index = (this->layer_view_index ^ 1); - brls::View *current_focus = brls::Application::getCurrentFocus(); + /* Don't proceed if we're already at the desired view layer. */ + if (index < 0 || index > 1 || (use_error_frame && index == 0) || (!use_error_frame && index == 1)) return; - /* Focus the sidebar if we're currently focusing an element from our List. */ - for(brls::View* list_view : this->list_views) + /* Focus the sidebar if we're currently focusing an element from our List and we're about to switch to the error frame. */ + if (use_error_frame && cur_focus && cur_focus->hasParent()) { - if (current_focus == list_view) - { - brls::Application::onGamepadButtonPressed(GLFW_GAMEPAD_BUTTON_DPAD_LEFT, false); - break; - } + brls::View *cur_focus_parent = cur_focus->getParent(); + if (cur_focus_parent && cur_focus_parent->hasParent() && cur_focus_parent->getParent() == this->list) brls::Application::onGamepadButtonPressed(GLFW_GAMEPAD_BUTTON_DPAD_LEFT, false); } /* Change layer view. */ - this->changeLayer(index); + this->changeLayer(new_index); this->invalidate(true); - this->layer_view_index = index; - } - - void LayeredErrorFrame::SetErrorFrameMessage(std::string msg) - { - this->error_frame->SetMessage(msg); - } - - void LayeredErrorFrame::AddListView(brls::View* view) - { - this->list->addView(view); - this->list_views.push_back(view); } } diff --git a/source/root_view.cpp b/source/root_view.cpp index 4396fbf..11d164b 100644 --- a/source/root_view.cpp +++ b/source/root_view.cpp @@ -22,7 +22,7 @@ #include #include #include -//#include +#include //#include //#include #include @@ -73,7 +73,7 @@ namespace nxdt::views /* Add tabs. */ this->addTab("root_view/tabs/gamecard"_i18n, new GameCardTab(this->gc_status_task)); this->addSeparator(); - this->addTab("root_view/tabs/user_titles"_i18n, new brls::Rectangle(nvgRGB(0, 255, 0))); + this->addTab("root_view/tabs/user_titles"_i18n, new UserTitlesTab(this->title_task)); this->addTab("root_view/tabs/system_titles"_i18n, new brls::Rectangle(nvgRGB(0, 0, 255))); this->addSeparator(); this->addTab("root_view/tabs/options"_i18n, new brls::Rectangle(nvgRGB(255, 255, 0))); diff --git a/source/user_titles_tab.cpp b/source/user_titles_tab.cpp new file mode 100644 index 0000000..31dfe7d --- /dev/null +++ b/source/user_titles_tab.cpp @@ -0,0 +1,100 @@ +/* + * user_titles_tab.cpp + * + * Copyright (c) 2020-2021, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +namespace i18n = brls::i18n; /* For getStr(). */ +using namespace i18n::literals; /* For _i18n. */ + +namespace nxdt::views +{ + UserTitlesTab::UserTitlesTab(nxdt::tasks::TitleTask *title_task) : LayeredErrorFrame("user_titles_tab/error_frame/no_titles_available"_i18n), title_task(title_task) + { + /* Populate list. */ + this->PopulateList(); + + /* Subscribe to title event. */ + this->title_task_sub = this->title_task->RegisterListener([this](void) { + /* Update list. */ + this->PopulateList(); + }); + } + + UserTitlesTab::~UserTitlesTab(void) + { + /* Unregister task listener. */ + this->title_task->UnregisterListener(this->title_task_sub); + } + + void UserTitlesTab::PopulateList(void) + { + bool refocus = false; + + this->user_app_metadata = this->title_task->GetApplicationMetadata(false); + size_t user_app_metadata_count = this->user_app_metadata->size(); + + if (user_app_metadata_count) + { + /* Determine if we need to refocus after updating the list. */ + brls::View *cur_view = brls::Application::getCurrentFocus(); + while(cur_view) + { + if (cur_view == this->list) + { + refocus = true; + break; + } + + cur_view = cur_view->getParent(); + } + } else { + /* If we need to, switch to the error frame *before* cleaning up our list. */ + this->SwitchLayerView(true); + } + + /* Clear list. */ + this->list->clear(); + this->list->invalidate(true); + + /* Immediately return if we have no user application metadata. */ + if (!user_app_metadata_count) return; + + /* Populate list. */ + for(TitleApplicationMetadata *cur_app_metadata : *(this->user_app_metadata)) + { + brls::ListItem *list_item = new brls::ListItem(std::string(cur_app_metadata->lang_entry.name), "", std::string(cur_app_metadata->lang_entry.author)); + list_item->setThumbnail(cur_app_metadata->icon, cur_app_metadata->icon_size); + this->list->addView(list_item); + } + + /* Switch to the list. */ + this->list->invalidate(true); + this->SwitchLayerView(false); + + /* Refocus, if needed. */ + if (refocus) + { + brls::Application::giveFocus(this->list->getChild(0)); + this->list->willAppear(true); + } + } +}