#include #include #include #include #include #include #include namespace ui { extern MainApplication *mainApp; namespace { constexpr int ListX = theme::space::lg; constexpr int ListW = 760; constexpr int PanelX = ListX + ListW + theme::space::xl; constexpr int PanelW = theme::layout::ScreenW - PanelX - theme::space::lg; constexpr int ContentY = theme::layout::ContentTop + theme::space::md; constexpr int ContentH = theme::layout::ContentH - 2 * theme::space::md; constexpr int BtnH = 56; constexpr int BtnW = PanelW - 2 * theme::space::lg; } TitlesLayout::TitlesLayout() : Layout::Layout() { using namespace theme; this->titlesMenu = pu::ui::elm::Menu::New( ListX, ContentY, ListW, color::BgBase, color::BgSurface2, 88, 6); this->titlesMenu->SetScrollbarColor(color::Primary); this->titlesMenu->SetItemsFocusColor(color::BgSurface2); this->titlesMenu->SetOnSelectionChanged([this]() { this->refreshPanel(); }); this->SetBackgroundColor(color::BgBase); this->Add(this->titlesMenu); this->panelBg = pu::ui::elm::Rectangle::New( PanelX, ContentY, PanelW, ContentH, color::BgSurface, radius::lg); this->Add(this->panelBg); this->panelTitle = pu::ui::elm::TextBlock::New( PanelX + space::lg, ContentY + space::lg, ""); this->panelTitle->SetFont(type::font(type::Title)); this->panelTitle->SetColor(color::TextPrimary); this->Add(this->panelTitle); this->panelHint = pu::ui::elm::TextBlock::New( PanelX + space::lg, ContentY + space::lg + 48, "Pick an action:"); this->panelHint->SetFont(type::font(type::Body)); this->panelHint->SetColor(color::TextSecondary); this->Add(this->panelHint); int btnY = ContentY + 200; this->btnTransferBg = pu::ui::elm::Rectangle::New( PanelX + space::lg, btnY, BtnW, BtnH, color::BgSurface2, radius::md); this->Add(this->btnTransferBg); this->btnTransferText = pu::ui::elm::TextBlock::New( PanelX + space::lg + space::md, btnY + 14, "Transfer to PC"); this->btnTransferText->SetFont(type::font(type::Body)); this->btnTransferText->SetColor(color::TextSecondary); this->Add(this->btnTransferText); int btnY2 = btnY + BtnH + space::md; this->btnReceiveBg = pu::ui::elm::Rectangle::New( PanelX + space::lg, btnY2, BtnW, BtnH, color::BgSurface2, radius::md); this->Add(this->btnReceiveBg); this->btnReceiveText = pu::ui::elm::TextBlock::New( PanelX + space::lg + space::md, btnY2 + 14, "Receive from PC"); this->btnReceiveText->SetFont(type::font(type::Body)); this->btnReceiveText->SetColor(color::TextSecondary); this->Add(this->btnReceiveText); this->panelFooter = pu::ui::elm::TextBlock::New( PanelX + space::lg, ContentY + ContentH - space::lg - 18, "Save data only"); this->panelFooter->SetFont(type::font(type::Caption)); this->panelFooter->SetColor(color::TextMuted); this->Add(this->panelFooter); this->emptyText = pu::ui::elm::TextBlock::New( ListX + ListW / 2 - 280, ContentY + ContentH / 2 - 40, "No save data on this profile"); this->emptyText->SetFont(type::font(type::Display)); this->emptyText->SetColor(color::TextPrimary); this->emptyText->SetVisible(false); this->Add(this->emptyText); this->emptySub = pu::ui::elm::TextBlock::New( ListX + ListW / 2 - 220, ContentY + ContentH / 2 + 16, "Play something first, then come back."); this->emptySub->SetFont(type::font(type::Body)); this->emptySub->SetColor(color::TextMuted); this->emptySub->SetVisible(false); this->Add(this->emptySub); this->header = std::make_unique(this, "Save Transfer"); this->hints = std::make_unique(this); this->updateHints(); } void TitlesLayout::InitTitles() { using namespace theme; Logger::getInstance().log(Logger::INFO, "InitTitles"); auto it = this->menuCache.find(g_currentUId); std::vector* items; if (it != this->menuCache.end()) { items = &it->second; } else { std::vector built; for (size_t i = 0; i < getTitleCount(g_currentUId); i++) { Title title; getTitle(title, g_currentUId, i); auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str()); titleItem->SetColor(color::TextPrimary); built.push_back(titleItem); } auto inserted = this->menuCache.emplace(g_currentUId, std::move(built)); items = &inserted.first->second; } this->titlesMenu->ClearItems(); for (auto& item : *items) { this->titlesMenu->AddItem(item); } this->titlesMenu->SetSelectedIndex(0); const bool empty = items->empty(); this->titlesMenu->SetVisible(!empty); this->panelBg->SetVisible(!empty); this->panelTitle->SetVisible(!empty); this->panelHint->SetVisible(!empty); this->btnTransferBg->SetVisible(!empty); this->btnTransferText->SetVisible(!empty); this->btnReceiveBg->SetVisible(!empty); this->btnReceiveText->SetVisible(!empty); this->panelFooter->SetVisible(!empty); this->emptyText->SetVisible(empty); this->emptySub->SetVisible(empty); this->focus = TitlesFocus::List; this->action = TitlesAction::Transfer; this->refreshPanel(); this->refreshButtons(); this->updateHints(); this->header->SetUser(g_currentUId, Account::username(g_currentUId)); } void TitlesLayout::refreshPanel() { if (this->titlesMenu->GetItems().empty()) return; int idx = this->titlesMenu->GetSelectedIndex(); Title title; getTitle(title, g_currentUId, idx); this->panelTitle->SetText(StringUtils::elide(title.name(), 24)); } void TitlesLayout::refreshButtons() { using namespace theme; const bool active = (focus == TitlesFocus::Actions); if (active && action == TitlesAction::Transfer) { this->btnTransferBg->SetColor(color::Primary); this->btnTransferText->SetColor(color::TextPrimary); this->btnReceiveBg->SetColor(color::BgSurface2); this->btnReceiveText->SetColor(color::TextSecondary); } else if (active && action == TitlesAction::Receive) { this->btnTransferBg->SetColor(color::BgSurface2); this->btnTransferText->SetColor(color::TextSecondary); this->btnReceiveBg->SetColor(color::Accent); this->btnReceiveText->SetColor(color::BgBase); } else { this->btnTransferBg->SetColor(color::BgSurface2); this->btnTransferText->SetColor(color::TextSecondary); this->btnReceiveBg->SetColor(color::BgSurface2); this->btnReceiveText->SetColor(color::TextSecondary); } } void TitlesLayout::updateHints() { if (focus == TitlesFocus::List) { this->hints->SetHints({{"A", "Choose action"}, {"B", "Back"}, {"+", "Quit"}}); } else { this->hints->SetHints({{"A", "Confirm"}, {"B", "Back"}}); } } void TitlesLayout::runTransfer(int index, Title& title) { auto ovl = TransferOverlay::New("Transferring save data..."); this->titlesMenu->SetVisible(false); mainApp->StartOverlay(ovl); this->LockInput(); if (transfer_files(index, g_currentUId) != 0) { mainApp->EndOverlay(); this->titlesMenu->SetVisible(true); this->UnlockInput(); mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true); return; } while (!isClientTransferDone()) { ovl->SetStatus(getClientStatusText()); ovl->SetProgressVisible(isClientProgressKnown()); ovl->SetProgress(getClientProgress()); mainApp->CallForRender(); if (mainApp->GetButtonsDown() & HidNpadButton_B) { cancelClientTransfer(); } svcSleepThread(16666666LL); } mainApp->EndOverlay(); this->titlesMenu->SetVisible(true); this->UnlockInput(); if (isClientConnectionFailed()) { mainApp->CreateShowDialog("Transfer", getClientFailReason(), {"OK"}, true); } else if (isClientTransferCancelled()) { mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true); } else { mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true); } } void TitlesLayout::runReceive(int index, Title& title) { if (startSendingThread() != 0) { mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true); return; } auto ovl = TransferOverlay::New("Receiving save data..."); this->titlesMenu->SetVisible(false); mainApp->StartOverlay(ovl); this->LockInput(); while (!isServerTransferDone()) { ovl->SetStatus(getServerStatusText()); ovl->SetProgress(getServerProgress()); mainApp->CallForRender(); if (mainApp->GetButtonsDown() & HidNpadButton_B) { cancelServerTransfer(); } svcSleepThread(16666666LL); } mainApp->EndOverlay(); this->titlesMenu->SetVisible(true); this->UnlockInput(); if (isServerTransferCancelled()) { mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true); return; } auto restoreResult = io::restore(index, g_currentUId, 0, title.name()); if (std::get<0>(restoreResult)) { mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true); } else { mainApp->CreateShowDialog("Receive", "Restore failed:\n" + std::get<2>(restoreResult), {"OK"}, true); } } void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) { if (m_inputLocked) return; if (Down & HidNpadButton_Plus) { cancelClientTransfer(); cancelServerTransfer(); mainApp->Close(); return; } if (focus == TitlesFocus::List) { if (Down & HidNpadButton_B) { this->header->SetUser(std::nullopt, ""); mainApp->LoadLayout(mainApp->usersLayout); return; } if (Down & HidNpadButton_A) { if (this->titlesMenu->GetItems().empty()) return; this->lockedListIndex = this->titlesMenu->GetSelectedIndex(); this->focus = TitlesFocus::Actions; this->action = TitlesAction::Transfer; this->refreshButtons(); this->updateHints(); return; } } else { if (this->titlesMenu->GetSelectedIndex() != this->lockedListIndex) { this->titlesMenu->SetSelectedIndex(this->lockedListIndex); } if (Down & HidNpadButton_B) { this->focus = TitlesFocus::List; this->refreshButtons(); this->updateHints(); return; } if (Down & (HidNpadButton_Up | HidNpadButton_Down | HidNpadButton_StickLUp | HidNpadButton_StickLDown)) { this->action = (action == TitlesAction::Transfer) ? TitlesAction::Receive : TitlesAction::Transfer; this->refreshButtons(); return; } if (Down & HidNpadButton_A) { int idx = this->titlesMenu->GetSelectedIndex(); Title title; getTitle(title, g_currentUId, idx); TitlesAction chosen = action; this->focus = TitlesFocus::List; this->refreshButtons(); this->updateHints(); if (chosen == TitlesAction::Transfer) { this->runTransfer(idx, title); } else { this->runReceive(idx, title); } } } } }