49efcde301
CI / Build NRO (push) Successful in 29s
CI / Format check (push) Successful in 28s
CI / Layering check (push) Successful in 2s
CI / Build NRO (pull_request) Successful in 29s
CI / Format check (pull_request) Successful in 27s
CI / Layering check (pull_request) Successful in 1s
313 lines
13 KiB
C++
313 lines
13 KiB
C++
#include <nxst/app/main_application.hpp>
|
|
#include <nxst/domain/util.hpp>
|
|
#include <nxst/ui/transfer_overlay.hpp>
|
|
|
|
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;
|
|
} // namespace
|
|
|
|
TitlesLayout::TitlesLayout() {
|
|
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 another device");
|
|
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 another device");
|
|
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<HeaderBar>(this, "Save Transfer");
|
|
this->hints = std::make_unique<HintBar>(this);
|
|
this->updateHints();
|
|
}
|
|
|
|
void TitlesLayout::InitTitles(AccountUid uid) {
|
|
using namespace theme;
|
|
this->current_uid = uid;
|
|
|
|
auto it = this->menuCache.find(uid);
|
|
std::vector<pu::ui::elm::MenuItem::Ref>* items;
|
|
if (it != this->menuCache.end()) {
|
|
items = &it->second;
|
|
} else {
|
|
std::vector<pu::ui::elm::MenuItem::Ref> built;
|
|
for (size_t i = 0; i < getTitleCount(uid); i++) {
|
|
Title title;
|
|
getTitle(title, uid, 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(uid, 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(uid, account::username(uid));
|
|
}
|
|
|
|
void TitlesLayout::refreshPanel() {
|
|
if (this->titlesMenu->GetItems().empty())
|
|
return;
|
|
int idx = this->titlesMenu->GetSelectedIndex();
|
|
Title title;
|
|
getTitle(title, this->current_uid, idx);
|
|
this->panelTitle->SetText(string_utils::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) {
|
|
(void)title;
|
|
auto ovl = TransferOverlay::New("Transferring save data...");
|
|
this->titlesMenu->SetVisible(false);
|
|
mainApp->StartOverlay(ovl);
|
|
this->LockInput();
|
|
if (mainApp->transfer.startSend((size_t)index, this->current_uid) != 0) {
|
|
mainApp->EndOverlay();
|
|
this->titlesMenu->SetVisible(true);
|
|
this->UnlockInput();
|
|
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
|
|
return;
|
|
}
|
|
while (!mainApp->transfer.isSendDone()) {
|
|
ovl->SetStatus(mainApp->transfer.sendStatusText());
|
|
ovl->SetProgressVisible(mainApp->transfer.isSendProgressKnown());
|
|
ovl->SetProgress(mainApp->transfer.sendProgress());
|
|
mainApp->CallForRender();
|
|
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
|
mainApp->transfer.cancelSend();
|
|
}
|
|
svcSleepThread(16666666LL);
|
|
}
|
|
mainApp->EndOverlay();
|
|
this->titlesMenu->SetVisible(true);
|
|
this->UnlockInput();
|
|
|
|
if (mainApp->transfer.isSendConnectionFailed()) {
|
|
mainApp->CreateShowDialog("Transfer", mainApp->transfer.sendFailReason(), {"OK"}, true);
|
|
} else if (mainApp->transfer.isSendCancelled()) {
|
|
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 (mainApp->transfer.startReceive((size_t)index, this->current_uid, title.name()) != 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 (!mainApp->transfer.isReceiveDone()) {
|
|
ovl->SetStatus(mainApp->transfer.receiveStatusText());
|
|
ovl->SetProgress(mainApp->transfer.receiveProgress());
|
|
mainApp->CallForRender();
|
|
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
|
|
mainApp->transfer.cancelReceive();
|
|
}
|
|
svcSleepThread(16666666LL);
|
|
}
|
|
mainApp->EndOverlay();
|
|
this->titlesMenu->SetVisible(true);
|
|
this->UnlockInput();
|
|
|
|
if (mainApp->transfer.isReceiveCancelled()) {
|
|
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
|
|
} else if (mainApp->transfer.restoreSucceeded()) {
|
|
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"},
|
|
true);
|
|
} else {
|
|
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + mainApp->transfer.restoreError(),
|
|
{"OK"}, true);
|
|
}
|
|
}
|
|
|
|
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
|
(void)Up;
|
|
(void)Held;
|
|
(void)Pos;
|
|
if (m_inputLocked)
|
|
return;
|
|
|
|
if (Down & HidNpadButton_Plus) {
|
|
mainApp->transfer.cancelSend();
|
|
mainApp->transfer.cancelReceive();
|
|
mainApp->Close();
|
|
return;
|
|
}
|
|
|
|
if (focus == TitlesFocus::List) {
|
|
if (Down & HidNpadButton_B) {
|
|
this->header->SetUser(std::nullopt, "");
|
|
mainApp->LoadLayout(mainApp->users_layout);
|
|
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, this->current_uid, 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // namespace ui
|