diff --git a/sphaira/include/ui/sidebar.hpp b/sphaira/include/ui/sidebar.hpp index ca279c5..82d24b5 100644 --- a/sphaira/include/ui/sidebar.hpp +++ b/sphaira/include/ui/sidebar.hpp @@ -140,7 +140,7 @@ public: m_callback = cb; } - auto GetValue() const { + auto GetValue() const -> const std::string& { return m_value; } @@ -155,14 +155,12 @@ private: class SidebarEntryTextInput final : public SidebarEntryTextBase { public: - explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide = {}, const std::string& info = ""); - - void SetGuide(const std::string& guide) { - m_guide = guide; - } + explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide = {}, s64 len_min = -1, s64 len_max = FS_MAX_PATH, const std::string& info = ""); private: - std::string m_guide; + const std::string m_guide; + const s64 m_len_min; + const s64 m_len_max; }; class SidebarEntryFilePicker final : public SidebarEntryTextBase { @@ -178,7 +176,7 @@ private: std::vector m_filter{}; }; -class Sidebar final : public Widget { +class Sidebar : public Widget { public: enum class Side { LEFT, RIGHT }; using Items = std::vector>; @@ -197,8 +195,8 @@ public: auto Add(std::unique_ptr&& entry) -> SidebarEntryBase*; template - auto Add(Args&&... args) -> SidebarEntryBase* { - return Add(std::make_unique(std::forward(args)...)); + auto Add(Args&&... args) -> T* { + return (T*)Add(std::make_unique(std::forward(args)...)); } private: diff --git a/sphaira/source/owo.cpp b/sphaira/source/owo.cpp index f9e0430..56d8caf 100644 --- a/sphaira/source/owo.cpp +++ b/sphaira/source/owo.cpp @@ -843,7 +843,7 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor pbox->SetImageDataConst(config.icon); R_UNLESS(!config.nro_path.empty(), Result_OwoBadArgs); - // R_UNLESS(!config.icon.empty(), OwoError_BadArgs); + R_UNLESS(!config.icon.empty(), Result_OwoBadArgs); R_TRY(splCryptoInitialize()); ON_SCOPE_EXIT(splCryptoExit()); diff --git a/sphaira/source/ui/menus/filebrowser.cpp b/sphaira/source/ui/menus/filebrowser.cpp index 0612fcf..1edc5c0 100644 --- a/sphaira/source/ui/menus/filebrowser.cpp +++ b/sphaira/source/ui/menus/filebrowser.cpp @@ -38,12 +38,33 @@ #include #include #include -// #include #include namespace sphaira::ui::menu::filebrowser { namespace { +using RomDatabaseIndexs = std::vector; + +struct ForwarderForm final : public Sidebar { + explicit ForwarderForm(const FileAssocEntry& assoc, const RomDatabaseIndexs& db_indexs, const FileEntry& entry, const fs::FsPath& arg_path); + +private: + auto LoadNroMeta() -> Result; + +private: + const FileAssocEntry m_assoc; + const RomDatabaseIndexs m_db_indexs; + const fs::FsPath m_arg_path; + + NroEntry m_nro{}; + NacpStruct m_nacp{}; + + SidebarEntryTextInput* m_name{}; + SidebarEntryTextInput* m_author{}; + SidebarEntryTextInput* m_version{}; + SidebarEntryFilePicker* m_icon{}; +}; + constinit UEvent g_change_uevent; constexpr FsEntry FS_ENTRY_DEFAULT{ @@ -176,7 +197,6 @@ auto IsExtension(std::string_view ext1, std::string_view ext2) -> bool { // tries to find database path using folder name // names are taken from retropie // retroarch database names can also be used -using RomDatabaseIndexs = std::vector; auto GetRomDatabaseFromPath(std::string_view path) -> RomDatabaseIndexs { if (path.length() <= 1) { return {}; @@ -216,7 +236,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> RomDatabaseIndexs { } // -auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDatabaseIndexs& db_indexs, const NroEntry& nro) { +auto GetRomIcon(std::string filename, const RomDatabaseIndexs& db_indexs, const NroEntry& nro) { // if no db entries, use nro icon if (db_indexs.empty()) { log_write("using nro image\n"); @@ -234,8 +254,6 @@ auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDa } } - #define RA_BOXART_URL "https://thumbnails.libretro.com/" - #define GH_BOXART_URL "https://raw.githubusercontent.com/libretro-thumbnails/" #define RA_BOXART_NAME "/Named_Boxarts/" #define RA_THUMBNAIL_PATH "/retroarch/thumbnails/" #define RA_BOXART_EXT ".png" @@ -249,42 +267,15 @@ auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDa } } - std::string filename_gh; - filename_gh.reserve(filename.size()); - for (auto c : filename) { - if (c == ' ') { - filename_gh += "%20"; - } else { - filename_gh.push_back(c); - } - } - const std::string thumbnail_path = system_name + RA_BOXART_NAME + filename + RA_BOXART_EXT; const std::string ra_thumbnail_path = RA_THUMBNAIL_PATH + thumbnail_path; - const std::string ra_thumbnail_url = RA_BOXART_URL + thumbnail_path; - const std::string gh_thumbnail_url = GH_BOXART_URL + system_name_gh + RA_BOXART_NAME + filename_gh + RA_BOXART_EXT; log_write("starting image convert on: %s\n", ra_thumbnail_path.c_str()); + // try and find icon locally - if (!pbox->ShouldExit()) { - pbox->NewTransfer("Trying to load "_i18n + ra_thumbnail_path); - std::vector image_file; - if (R_SUCCEEDED(fs->read_entire_file(ra_thumbnail_path.c_str(), image_file))) { - return image_file; - } - } - - // try and download icon - if (!pbox->ShouldExit()) { - pbox->NewTransfer("Downloading "_i18n + gh_thumbnail_url); - const auto result = curl::Api().ToMemory( - curl::Url{gh_thumbnail_url}, - curl::OnProgress{pbox->OnDownloadProgressCallback()} - ); - - if (result.success && !result.data.empty()) { - return result.data; - } + std::vector image_file; + if (R_SUCCEEDED(fs::FsNativeSd().read_entire_file(ra_thumbnail_path, image_file))) { + return image_file; } } @@ -293,6 +284,116 @@ auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDa return nro_get_icon(nro.path, nro.icon_size, nro.icon_offset); } +ForwarderForm::ForwarderForm(const FileAssocEntry& assoc, const RomDatabaseIndexs& db_indexs, const FileEntry& entry, const fs::FsPath& arg_path) +: Sidebar{"Forwarder Creation", Side::RIGHT} +, m_assoc{assoc} +, m_db_indexs{db_indexs} +, m_arg_path{arg_path} { + log_write("parsing nro\n"); + if (R_FAILED(LoadNroMeta())) { + App::Notify("Failed to parse nro"_i18n); + SetPop(); + return; + } + + log_write("got nro data\n"); + auto file_name = m_assoc.use_base_name ? entry.GetName() : entry.GetInternalName(); + + 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 name = m_nro.nacp.lang.name + std::string{" | "} + file_name; + const auto author = m_nacp.lang[0].author; + const auto version = m_nacp.display_version; + const auto icon = m_assoc.path; + + m_name = this->Add( + "Name", name, "", -1, sizeof(NacpLanguageEntry::name) - 1, + "Set the name of the application"_i18n + ); + + m_author = this->Add( + "Author", author, "", -1, sizeof(NacpLanguageEntry::author) - 1, + "Set the author of the application"_i18n + ); + + m_version = this->Add( + "Version", version, "", -1, sizeof(NacpStruct::display_version) - 1, + "Set the display version of the application"_i18n + ); + + const std::vector filters{".nro", ".png", ".jpg"}; + m_icon = this->Add( + "Icon", icon, filters, + "Set the path to the icon for the forwarder"_i18n + ); + + auto callback = this->Add("Create", [this, file_name](){ + OwoConfig config{}; + config.nro_path = m_assoc.path.toString(); + config.args = nro_add_arg_file(m_arg_path); + config.nacp = m_nacp; + + // patch the name. + config.name = m_name->GetValue(); + + // patch the author. + config.author = m_author->GetValue(); + + // patch the display version. + std::snprintf(config.nacp.display_version, sizeof(config.nacp.display_version), "%s", m_version->GetValue().c_str()); + + // load icon fron nro or image. + if (m_icon->GetValue().ends_with(".nro")) { + // if path was left as the default, try and load the icon from rom db. + if (config.nro_path == m_icon->GetValue()) { + config.icon = GetRomIcon(file_name, m_db_indexs, m_nro); + } else { + config.icon = nro_get_icon(m_icon->GetValue()); + } + } else { + // try and read icon file into memory, bail if this fails. + const auto rc = fs::FsStdio().read_entire_file(m_icon->GetValue(), config.icon); + if (R_FAILED(rc)) { + App::PushErrorBox(rc, "Failed to load icon"); + return; + } + } + + // if this is a rom, load intro logo. + if (!m_db_indexs.empty()) { + fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/NintendoLogo.png", config.logo); + fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/StartupMovie.gif", config.gif); + } + + // try and install. + if (R_FAILED(App::Install(config))) { + App::Notify("Failed to install forwarder"_i18n); + } else { + SetPop(); + } + }, "Create the forwarder."_i18n); + + // ensure that all fields are valid. + callback->Depends([this](){ + return + !m_name->GetValue().empty() && + !m_author->GetValue().empty() && + !m_version->GetValue().empty() && + !m_icon->GetValue().empty(); + }, "All fields must be non-empty!"_i18n); +} + +auto ForwarderForm::LoadNroMeta() -> Result { + // try and load nro meta data. + R_TRY(nro_parse(m_assoc.path, m_nro)); + R_TRY(nro_get_nacp(m_assoc.path, m_nacp)); + R_SUCCEED(); +} + } // namespace void SignalChange() { @@ -650,51 +751,7 @@ void FsView::InstallForwarder() { title, items, [this, assoc_list](auto op_index){ if (op_index) { const auto assoc = assoc_list[*op_index]; - log_write("pushing it\n"); - App::Push(0, "Installing Forwarder"_i18n, GetEntry().name, [assoc, this](auto pbox) -> Result { - log_write("inside callback\n"); - - NroEntry nro{}; - log_write("parsing nro\n"); - R_TRY(nro_parse(assoc.path, nro)); - - NacpStruct nacp; - R_TRY(nro_get_nacp(assoc.path, nacp)); - - log_write("got nro data\n"); - auto file_name = assoc.use_base_name ? GetEntry().GetName() : GetEntry().GetInternalName(); - - 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 db_indexs = GetRomDatabaseFromPath(m_path); - - OwoConfig config{}; - config.nro_path = assoc.path.toString(); - config.args = nro_add_arg_file(GetNewPathCurrent()); - config.name = nro.nacp.lang.name + std::string{" | "} + file_name; - // config.name = file_name; - config.nacp = nacp; - config.icon = GetRomIcon(m_fs.get(), pbox, file_name, db_indexs, nro); - pbox->SetImageDataConst(config.icon); - - if (!db_indexs.empty()) { - fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/NintendoLogo.png", config.logo); - fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/StartupMovie.gif", config.gif); - } - - return App::Install(pbox, config); - }, [this](Result rc){ - App::PushErrorBox(rc, "Failed to install forwarder"_i18n); - - if (R_SUCCEEDED(rc)) { - App::PlaySoundEffect(SoundEffect_Install); - App::Notify("Installed!"_i18n); - } - }); + App::Push(assoc, GetRomDatabaseFromPath(m_path), GetEntry(), GetNewPathCurrent()); } else { log_write("pressed B to skip launch...\n"); } diff --git a/sphaira/source/ui/sidebar.cpp b/sphaira/source/ui/sidebar.cpp index a071486..8b2f664 100644 --- a/sphaira/source/ui/sidebar.cpp +++ b/sphaira/source/ui/sidebar.cpp @@ -252,19 +252,17 @@ SidebarEntryTextBase::SidebarEntryTextBase(const std::string& title, const std:: void SidebarEntryTextBase::Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) { SidebarEntryBase::Draw(vg, theme, root_pos, left); SidebarEntryBase::DrawEntry(vg, theme, m_title, m_value, true); - - // const auto colour_id = IsEnabled() ? ThemeEntryID_TEXT : ThemeEntryID_TEXT_INFO; - // const auto max_w = m_pos.w - 15.f * 2; - - // m_scolling_title.Draw(vg, HasFocus(), m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f), max_w, 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour_id), m_title); } -SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide, const std::string& info) -: SidebarEntryTextBase{title, value, {}, info}, m_guide{guide} { +SidebarEntryTextInput::SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& guide, s64 len_min, s64 len_max, const std::string& info) +: SidebarEntryTextBase{title, value, {}, info} +, m_guide{guide} +, m_len_min{len_min} +, m_len_max{len_max} { SetCallback([this](){ std::string out; - if (R_SUCCEEDED(swkbd::ShowText(out, m_guide.c_str(), GetValue().c_str()))) { + if (R_SUCCEEDED(swkbd::ShowText(out, m_guide.c_str(), GetValue().c_str(), m_len_min, m_len_max))) { SetValue(out); } }); @@ -347,30 +345,6 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void { } auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void { - // Vec4 info_box{}; - // info_box.y = m_top_bar.y; - // info_box.w = 300; - // info_box.h = 250; - - // if (m_side == Side::LEFT) { - // info_box.x = m_pos.x + m_pos.w + 10; - // } else { - // info_box.x = m_pos.x - info_box.w - 10; - // } - - // const float info_pad = 30; - // const float info_font_size = 18; - // const char* msg = "Skips verifying the nca header signature"; - // float bounds[4]; - // nvgFontSize(vg, info_font_size); - // nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); - // nvgTextLineHeight(vg, 1.7); - // nvgTextBoxBounds(vg, info_box.x + info_pad, info_box.y + info_pad, info_box.w - info_pad * 2, msg, nullptr, bounds); - // info_box.h = info_pad * 2 + bounds[3] - bounds[1]; - - // gfx::drawRect(vg, info_box, theme->GetColour(ThemeEntryID_SIDEBAR), 5); - // gfx::drawTextBox(vg, bounds[0], bounds[1], info_font_size, info_box.w - info_pad * 2, theme->GetColour(ThemeEntryID_TEXT), msg); - gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR)); gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str()); if (!m_sub.empty()) {