diff --git a/CMakeLists.txt b/CMakeLists.txt index eb66586310bb..4a7c54de6cf4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -820,6 +820,7 @@ set(NativeAppSource UI/OnScreenDisplay.cpp UI/ControlMappingScreen.cpp UI/ReportScreen.cpp + UI/SavedataScreen.cpp UI/Store.cpp UI/CwCheatScreen.cpp UI/InstallZipScreen.cpp diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index ecba48f4f4a2..10a14abb7ba5 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -317,8 +317,6 @@ static void __IoFreeFd(int fd, u32 &error) { // TODO: We don't do any of that yet. // For now, let's at least delay the callback notification. static void __IoAsyncNotify(u64 userdata, int cyclesLate) { - PROFILE_THIS_SCOPE("io_rw"); - int fd = (int) userdata; u32 error; diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index bc2063e99348..72468c65474e 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -758,7 +758,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader) } std::string extension = fileLoader->Extension(); - if (!strcasecmp(extension.c_str(),".iso")) + if (!strcasecmp(extension.c_str(), ".iso")) { // may be a psx iso, they have 2352 byte sectors. You never know what some people try to open if ((fileLoader->FileSize() % 2352) == 0) @@ -780,26 +780,36 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader) { return FILETYPE_PSP_ISO; } - + else if (!strcasecmp(extension.c_str(),".ppst")) + { + return FILETYPE_PPSSPP_SAVESTATE; + } // First, check if it's a directory with an EBOOT.PBP in it. if (fileLoader->IsDirectory()) { std::string filename = fileLoader->Path(); if (filename.size() > 4) { - FileInfo ebootInfo; + FileInfo fileInfo; // Check for existence of EBOOT.PBP, as required for "Directory games". - if (getFileInfo((filename + "/EBOOT.PBP").c_str(), &ebootInfo)) { - if (ebootInfo.exists) { + if (getFileInfo((filename + "/EBOOT.PBP").c_str(), &fileInfo)) { + if (fileInfo.exists) { return FILETYPE_PSP_PBP_DIRECTORY; } } // check if it's a disc directory - if (getFileInfo((filename + "/PSP_GAME").c_str(), &ebootInfo)) { - if (ebootInfo.exists) { + if (getFileInfo((filename + "/PSP_GAME").c_str(), &fileInfo)) { + if (fileInfo.exists) { return FILETYPE_PSP_DISC_DIRECTORY; } } + + // Not that, okay, let's guess it's a savedata directory if it has a param.sfo... + if (getFileInfo((filename + "/PARAM.SFO").c_str(), &fileInfo)) { + if (fileInfo.exists) { + return FILETYPE_PSP_SAVEDATA_DIRECTORY; + } + } } return FILETYPE_NORMAL_DIRECTORY; @@ -990,6 +1000,14 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { *error_string = "Just a directory."; break; + case FILETYPE_PPSSPP_SAVESTATE: + *error_string = "This is a saved state, not a game."; // Actually, we could make it load it... + break; + + case FILETYPE_PSP_SAVEDATA_DIRECTORY: + *error_string = "This is save data, not a game."; // Actually, we could make it load it... + break; + case FILETYPE_UNKNOWN_BIN: case FILETYPE_UNKNOWN_ELF: case FILETYPE_UNKNOWN: diff --git a/Core/Loaders.h b/Core/Loaders.h index f3b56668f3dd..c5dbbdc57954 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -43,6 +43,9 @@ enum IdentifiedFileType { FILETYPE_NORMAL_DIRECTORY, + FILETYPE_PSP_SAVEDATA_DIRECTORY, + FILETYPE_PPSSPP_SAVESTATE, + FILETYPE_UNKNOWN }; diff --git a/Core/System.cpp b/Core/System.cpp index 7f192f457386..149feb3f2feb 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -529,6 +529,8 @@ std::string GetSysDirectory(PSPDirectories directoryType) { return g_Config.memStickDirectory + "PAUTH/"; case DIRECTORY_DUMP: return g_Config.memStickDirectory + "PSP/SYSTEM/DUMP/"; + case DIRECTORY_SAVESTATE: + return g_Config.memStickDirectory + "PSP/PPSSPP_STATE/"; // Just return the memory stick root if we run into some sort of problem. default: ERROR_LOG(FILESYS, "Unknown directory type."); diff --git a/Core/System.h b/Core/System.h index 651aac07339c..217ccf7858d8 100644 --- a/Core/System.h +++ b/Core/System.h @@ -43,6 +43,7 @@ enum PSPDirectories { DIRECTORY_SAVEDATA, DIRECTORY_PAUTH, DIRECTORY_DUMP, + DIRECTORY_SAVESTATE, }; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 12ad39585b49..f3ca3915e82f 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -447,7 +447,7 @@ void SoftGPU::ExecuteOp(u32 op, u32 diff) } if (!(gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME)) { - TransformUnit::SubmitSpline(control_points, indices, sp_ucount, sp_vcount, sp_utype, sp_vtype, gstate.getPatchPrimitiveType(), gstate.vertType); + //TransformUnit::SubmitSpline(control_points, indices, sp_ucount, sp_vcount, sp_utype, sp_vtype, gstate.getPatchPrimitiveType(), gstate.vertType); } framebufferDirty_ = true; DEBUG_LOG(G3D,"DL DRAW SPLINE: %i x %i, %i x %i", sp_ucount, sp_vcount, sp_utype, sp_vtype); diff --git a/UI/DevScreens.h b/UI/DevScreens.h index 928f6576b777..3e150f6260a8 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -31,9 +31,8 @@ class DevMenu : public PopupScreen { public: DevMenu() : PopupScreen("Dev Tools") {} - virtual void CreatePopupContents(UI::ViewGroup *parent); - - virtual void dialogFinished(const Screen *dialog, DialogResult result); + void CreatePopupContents(UI::ViewGroup *parent) override; + void dialogFinished(const Screen *dialog, DialogResult result) override; protected: UI::EventReturn OnLogView(UI::EventParams &e); @@ -48,7 +47,7 @@ class DevMenu : public PopupScreen { class LogConfigScreen : public UIDialogScreenWithBackground { public: LogConfigScreen() {} - virtual void CreateViews(); + virtual void CreateViews() override; private: UI::EventReturn OnToggleAll(UI::EventParams &e); diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 4050b9c0f57c..7830de7268b0 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -48,7 +48,7 @@ GameInfo::~GameInfo() { delete fileLoader; } -bool GameInfo::DeleteGame() { +bool GameInfo::Delete() { switch (fileType) { case FILETYPE_PSP_ISO: case FILETYPE_PSP_ISO_NP: @@ -63,9 +63,9 @@ bool GameInfo::DeleteGame() { return true; } case FILETYPE_PSP_PBP_DIRECTORY: + case FILETYPE_PSP_SAVEDATA_DIRECTORY: { // TODO: This could be handled by Core/Util/GameManager too somehow. - const char *directoryToRemove = filePath_.c_str(); INFO_LOG(HLE, "Deleting %s", directoryToRemove); if (!File::DeleteDirRecursively(directoryToRemove)) { @@ -76,6 +76,12 @@ bool GameInfo::DeleteGame() { return true; } case FILETYPE_PSP_ELF: + case FILETYPE_PPSSPP_SAVESTATE: + case FILETYPE_UNKNOWN_BIN: + case FILETYPE_UNKNOWN_ELF: + case FILETYPE_ARCHIVE_RAR: + case FILETYPE_ARCHIVE_ZIP: + case FILETYPE_ARCHIVE_7Z: { const char *fileToRemove = filePath_.c_str(); deleteFile(fileToRemove); @@ -87,16 +93,35 @@ bool GameInfo::DeleteGame() { } } +static int64_t GetDirectoryRecursiveSize(std::string path) { + std::vector fileInfo; + getFilesInDir(path.c_str(), &fileInfo); + int64_t sizeSum = 0; + // Note: getFileInDir does not fill in fileSize properly. + for (size_t i = 0; i < fileInfo.size(); i++) { + FileInfo finfo; + getFileInfo(fileInfo[i].fullName.c_str(), &finfo); + if (!finfo.isDirectory) + sizeSum += finfo.size; + else + sizeSum += GetDirectoryRecursiveSize(finfo.fullName); + } + return sizeSum; +} + u64 GameInfo::GetGameSizeInBytes() { switch (fileType) { case FILETYPE_PSP_PBP_DIRECTORY: - // TODO: Need to recurse here. - return 0; + case FILETYPE_PSP_SAVEDATA_DIRECTORY: + { + return GetDirectoryRecursiveSize(filePath_); + } default: return GetFileLoader()->FileSize(); } } +// Not too meaningful if the object itself is a savedata directory... std::vector GameInfo::GetSaveDataDirectories() { std::string memc = GetSysDirectory(DIRECTORY_SAVEDATA); @@ -117,6 +142,9 @@ std::vector GameInfo::GetSaveDataDirectories() { } u64 GameInfo::GetSaveDataSizeInBytes() { + if (fileType == FILETYPE_PSP_SAVEDATA_DIRECTORY || fileType == FILETYPE_PPSSPP_SAVESTATE) { + return 0; + } std::vector saveDataDir = GetSaveDataDirectories(); u64 totalSize = 0; @@ -132,7 +160,7 @@ u64 GameInfo::GetSaveDataSizeInBytes() { filesSizeInDir += finfo.size; } if (filesSizeInDir < 0xA00000) { - //Generally the savedata size in a dir shouldn't be more than 10MB. + // HACK: Generally the savedata size in a dir shouldn't be more than 10MB. totalSize += filesSizeInDir; } filesSizeInDir = 0; @@ -141,6 +169,9 @@ u64 GameInfo::GetSaveDataSizeInBytes() { } u64 GameInfo::GetInstallDataSizeInBytes() { + if (fileType == FILETYPE_PSP_SAVEDATA_DIRECTORY || fileType == FILETYPE_PPSSPP_SAVESTATE) { + return 0; + } std::vector saveDataDir = GetSaveDataDirectories(); u64 totalSize = 0; @@ -156,7 +187,7 @@ u64 GameInfo::GetInstallDataSizeInBytes() { filesSizeInDir += finfo.size; } if (filesSizeInDir >= 0xA00000) { - // Generally the savedata size in a dir shouldn't be more than 10MB. + // HACK: Generally the savedata size in a dir shouldn't be more than 10MB. // This is probably GameInstall data. totalSize += filesSizeInDir; } @@ -371,6 +402,33 @@ class GameInfoWorkItem : public PrioritizedWorkQueueItem { } break; + case FILETYPE_PSP_SAVEDATA_DIRECTORY: + { + SequentialHandleAllocator handles; + VirtualDiscFileSystem umd(&handles, gamePath_.c_str()); + + // Alright, let's fetch the PARAM.SFO. + std::string paramSFOcontents; + if (ReadFileToString(&umd, "/PARAM.SFO", ¶mSFOcontents, 0)) { + lock_guard lock(info_->lock); + info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size()); + info_->ParseParamSFO(); + } + + ReadFileToString(&umd, "/ICON0.PNG", &info_->iconTextureData, &info_->lock); + info_->iconDataLoaded = true; + if (info_->wantFlags & GAMEINFO_WANTBG) { + ReadFileToString(&umd, "/PIC1.PNG", &info_->pic1TextureData, &info_->lock); + info_->pic1DataLoaded = true; + } + break; + } + + case FILETYPE_PPSSPP_SAVESTATE: + { + break; + } + case FILETYPE_PSP_DISC_DIRECTORY: { info_->fileType = FILETYPE_PSP_ISO; @@ -399,6 +457,7 @@ class GameInfoWorkItem : public PrioritizedWorkQueueItem { } break; } + case FILETYPE_PSP_ISO: case FILETYPE_PSP_ISO_NP: { @@ -517,7 +576,6 @@ class GameInfoWorkItem : public PrioritizedWorkQueueItem { }; - GameInfoCache::~GameInfoCache() { Clear(); } @@ -531,18 +589,6 @@ void GameInfoCache::Shutdown() { StopProcessingWorkQueue(gameInfoWQ_); } -void GameInfoCache::Save() { - // TODO -} - -void GameInfoCache::Load() { - // TODO -} - -void GameInfoCache::Decimate() { - // TODO -} - void GameInfoCache::Clear() { if (gameInfoWQ_) gameInfoWQ_->Flush(); @@ -610,6 +656,19 @@ void GameInfoCache::FlushBGs() { } } +void GameInfoCache::PurgeType(IdentifiedFileType fileType) { + if (gameInfoWQ_) + gameInfoWQ_->Flush(); + restart: + for (auto iter = info_.begin(); iter != info_.end(); iter++) { + if (iter->second->fileType == fileType) { + info_.erase(iter); + goto restart; + } + } +} + + // Runs on the main thread. GameInfo *GameInfoCache::GetInfo(Thin3DContext *thin3d, const std::string &gamePath, int wantFlags) { GameInfo *info = 0; diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 9addc9e4e013..2a26d22b5abc 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -33,6 +33,8 @@ class Thin3DTexture; // does on the PSP, namely checking for and deleting savedata, and similar things. // Only cares about games that are installed on the current device. +// A GameInfo object can also represent a piece of savedata. + // Guessed from GameID, not necessarily accurate enum GameRegion { GAMEREGION_JAPAN, @@ -94,14 +96,15 @@ class GameInfo { public: GameInfo() : disc_total(0), disc_number(0), region(-1), fileType(FILETYPE_UNKNOWN), paramSFOLoaded(false), - iconTexture(NULL), pic0Texture(NULL), pic1Texture(NULL), wantFlags(0), - timeIconWasLoaded(0.0), timePic0WasLoaded(0.0), timePic1WasLoaded(0.0), + iconTexture(nullptr), pic0Texture(nullptr), pic1Texture(nullptr), wantFlags(0), + lastAccessedTime(0.0), timeIconWasLoaded(0.0), timePic0WasLoaded(0.0), timePic1WasLoaded(0.0), gameSize(0), saveDataSize(0), installDataSize(0), fileLoader(nullptr) {} ~GameInfo(); - bool DeleteGame(); // Better be sure what you're doing when calling this. + bool Delete(); // Better be sure what you're doing when calling this. bool DeleteAllSaveData(); bool LoadFromPath(const std::string &gamePath); + FileLoader *GetFileLoader(); void DisposeFileLoader(); @@ -174,19 +177,15 @@ class GameInfoCache { void Init(); void Shutdown(); void Clear(); + void PurgeType(IdentifiedFileType fileType); // All data in GameInfo including iconTexture may be zero the first time you call this // but filled in later asynchronously in the background. So keep calling this, // redrawing the UI often. Only set flags to GAMEINFO_WANTBG or WANTSND if you really want them // because they're big. bgTextures and sound may be discarded over time as well. GameInfo *GetInfo(Thin3DContext *thin3d, const std::string &gamePath, int wantFlags); - void Decimate(); // Deletes old info. void FlushBGs(); // Gets rid of all BG textures. Also gets rid of bg sounds. - // TODO - save cache between sessions - void Save(); - void Load(); - PrioritizedWorkQueue *WorkQueue() { return gameInfoWQ_; } private: diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 6e37c99d477d..60263c61f7f6 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -265,7 +265,7 @@ UI::EventReturn GameScreen::OnDeleteGame(UI::EventParams &e) { void GameScreen::CallbackDeleteGame(bool yes) { GameInfo *info = g_gameInfoCache.GetInfo(NULL, gamePath_, 0); if (yes) { - info->DeleteGame(); + info->Delete(); g_gameInfoCache.Clear(); screenManager()->switchScreen(new MainScreen()); } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 1b2d997d9f59..b28e07ab1e62 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -33,6 +33,7 @@ #include "UI/MiscScreens.h" #include "UI/ControlMappingScreen.h" #include "UI/DevScreens.h" +#include "UI/SavedataScreen.h" #include "UI/TouchControlLayoutScreen.h" #include "UI/TouchControlVisibilityScreen.h" #include "UI/TiltAnalogSettingsScreen.h" @@ -464,6 +465,15 @@ void GameSettingsScreen::CreateViews() { networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in PRO Adhoc Server", "Enable built-in PRO Adhoc Server"))); networkingSettings->Add(new ChoiceWithValueDisplay(&g_Config.sMACAddress, n->T("Change Mac Address"), nullptr))->OnClick.Handle(this, &GameSettingsScreen::OnChangeMacAddress); + ViewGroup *toolsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); + LinearLayout *tools = new LinearLayout(ORIENT_VERTICAL); + tools->SetSpacing(0); + toolsScroll->Add(tools); + tabHolder->AddTab(ms->T("Tools"), toolsScroll); + + tools->Add(new ItemHeader(ms->T("Tools"))); + tools->Add(new Choice(n->T("Savedata Manager")))->OnClick.Handle(this, &GameSettingsScreen::OnSavedataManager); + // System ViewGroup *systemSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); LinearLayout *systemSettings = new LinearLayout(ORIENT_VERTICAL); @@ -963,6 +973,12 @@ UI::EventReturn GameSettingsScreen::OnTiltCustomize(UI::EventParams &e){ return UI::EVENT_DONE; }; +UI::EventReturn GameSettingsScreen::OnSavedataManager(UI::EventParams &e) { + auto saveData = new SavedataScreen(""); + screenManager()->push(saveData); + return UI::EVENT_DONE; +} + void DeveloperToolsScreen::CreateViews() { using namespace UI; root_ = new ScrollView(ORIENT_VERTICAL); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 6b80c81c4d74..2e4f9e65ced8 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -99,6 +99,8 @@ class GameSettingsScreen : public UIDialogScreenWithGameBackground { UI::EventReturn OnAdhocGuides(UI::EventParams &e); UI::EventReturn OnAudioBackend(UI::EventParams &e); + UI::EventReturn OnSavedataManager(UI::EventParams &e); + // Temporaries to convert bools to int settings bool cap60FPS_; int iAlternateSpeedPercent_; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 2ffd34cd6a5a..ad82a910b437 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -42,6 +42,7 @@ #include "UI/GameSettingsScreen.h" #include "UI/MiscScreens.h" #include "UI/ControlMappingScreen.h" +#include "UI/SavedataScreen.h" #include "UI/Store.h" #include "UI/ui_atlas.h" #include "Core/Config.h" @@ -394,40 +395,6 @@ void DirButton::Draw(UIContext &dc) { } } -class GameBrowser : public UI::LinearLayout { -public: - GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle_, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = 0); - - UI::Event OnChoice; - UI::Event OnHoldChoice; - UI::Event OnHighlight; - - UI::Choice *HomebrewStoreButton() { return homebrewStoreButton_; } -private: - void Refresh(); - bool IsCurrentPathPinned(); - const std::vector GetPinnedPaths(); - const std::string GetBaseName(const std::string &path); - - UI::EventReturn GameButtonClick(UI::EventParams &e); - UI::EventReturn GameButtonHoldClick(UI::EventParams &e); - UI::EventReturn GameButtonHighlight(UI::EventParams &e); - UI::EventReturn NavigateClick(UI::EventParams &e); - UI::EventReturn LayoutChange(UI::EventParams &e); - UI::EventReturn LastClick(UI::EventParams &e); - UI::EventReturn HomeClick(UI::EventParams &e); - UI::EventReturn PinToggleClick(UI::EventParams &e); - - UI::ViewGroup *gameList_; - PathBrowser path_; - bool *gridStyle_; - bool allowBrowsing_; - std::string lastText_; - std::string lastLink_; - int flags_; - UI::Choice *homebrewStoreButton_; -}; - GameBrowser::GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle, std::string lastText, std::string lastLink, int flags, UI::LayoutParams *layoutParams) : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), gameList_(0), path_(path), gridStyle_(gridStyle), allowBrowsing_(allowBrowsing), lastText_(lastText), lastLink_(lastLink), flags_(flags) { using namespace UI; @@ -539,13 +506,16 @@ void GameBrowser::Refresh() { path_.GetListing(fileInfo, "iso:cso:pbp:elf:prx:"); for (size_t i = 0; i < fileInfo.size(); i++) { bool isGame = !fileInfo[i].isDirectory; + bool isSaveData = false; // Check if eboot directory if (!isGame && path_.GetPath().size() >= 4 && File::Exists(path_.GetPath() + fileInfo[i].name + "/EBOOT.PBP")) isGame = true; else if (!isGame && File::Exists(path_.GetPath() + fileInfo[i].name + "/PSP_GAME/SYSDIR")) isGame = true; + else if (!isGame && File::Exists(path_.GetPath() + fileInfo[i].name + "/PARAM.SFO")) + isSaveData = true; - if (!isGame) { + if (!isGame && !isSaveData) { if (allowBrowsing_) { dirButtons.push_back(new DirButton(fileInfo[i].name, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); } @@ -1000,6 +970,12 @@ UI::EventReturn MainScreen::OnGameSelected(UI::EventParams &e) { #else std::string path = e.s; #endif + GameInfo *ginfo = 0; + ginfo = g_gameInfoCache.GetInfo(nullptr, path, GAMEINFO_WANTBG); + if (ginfo && ginfo->fileType == FILETYPE_PSP_SAVEDATA_DIRECTORY) { + return UI::EVENT_DONE; + } + SetBackgroundAudioGame(path); lockBackgroundAudio_ = true; screenManager()->push(new GameScreen(path)); @@ -1186,6 +1162,7 @@ UI::EventReturn UmdReplaceScreen::OnGameSettings(UI::EventParams &e) { screenManager()->push(new GameSettingsScreen("")); return UI::EVENT_DONE; } + UI::EventReturn UmdReplaceScreen::OnGameSelectedInstant(UI::EventParams &e) { __UmdReplace(e.s); screenManager()->finishDialog(this, DR_OK); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 4e9e76df56ad..6ef9018a1485 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -18,13 +18,45 @@ #pragma once #include "base/functional.h" +#include "file/path.h" #include "ui/ui_screen.h" #include "ui/viewgroup.h" #include "UI/MiscScreens.h" -// Game screen: Allows you to start a game, delete saves, delete the game, -// set game specific settings, etc. -// Uses GameInfoCache heavily to implement the functionality. +class GameBrowser : public UI::LinearLayout { +public: + GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle_, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = 0); + + UI::Event OnChoice; + UI::Event OnHoldChoice; + UI::Event OnHighlight; + + UI::Choice *HomebrewStoreButton() { return homebrewStoreButton_; } + +private: + void Refresh(); + bool IsCurrentPathPinned(); + const std::vector GetPinnedPaths(); + const std::string GetBaseName(const std::string &path); + + UI::EventReturn GameButtonClick(UI::EventParams &e); + UI::EventReturn GameButtonHoldClick(UI::EventParams &e); + UI::EventReturn GameButtonHighlight(UI::EventParams &e); + UI::EventReturn NavigateClick(UI::EventParams &e); + UI::EventReturn LayoutChange(UI::EventParams &e); + UI::EventReturn LastClick(UI::EventParams &e); + UI::EventReturn HomeClick(UI::EventParams &e); + UI::EventReturn PinToggleClick(UI::EventParams &e); + + UI::ViewGroup *gameList_; + PathBrowser path_; + bool *gridStyle_; + bool allowBrowsing_; + std::string lastText_; + std::string lastLink_; + int flags_; + UI::Choice *homebrewStoreButton_; +}; class MainScreen : public UIScreenWithBackground { public: diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 45239855b30b..ede629b0bc38 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -149,7 +149,12 @@ void UIScreenWithBackground::DrawBackground(UIContext &dc) { } void UIScreenWithGameBackground::DrawBackground(UIContext &dc) { - DrawGameBackground(dc, gamePath_); + if (!gamePath_.empty()) { + DrawGameBackground(dc, gamePath_); + } else { + ::DrawBackground(dc, 1.0f); + dc.Flush(); + } } void UIDialogScreenWithGameBackground::DrawBackground(UIContext &dc) { diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 0e7a8765613d..f6254e82e258 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -140,57 +140,13 @@ class ScreenshotViewScreen : public PopupScreen { class SaveSlotView : public UI::LinearLayout { public: - SaveSlotView(int slot, UI::LayoutParams *layoutParams = nullptr) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), slot_(slot) { - using namespace UI; - - screenshotFilename_ = SaveState::GenerateSaveSlotFilename(slot, "jpg"); - PrioritizedWorkQueue *wq = g_gameInfoCache.WorkQueue(); - Add(new Spacer(5)); - - AsyncImageFileView *fv = Add(new AsyncImageFileView(screenshotFilename_, IS_DEFAULT, wq, new UI::LayoutParams(82 * 2, 47 * 2))); - fv->SetOverlayText(StringFromFormat("%i", slot_ + 1)); - - I18NCategory *i = GetI18NCategory("Pause"); - - LinearLayout *buttons = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - buttons->SetSpacing(2.0); - Add(buttons); - - saveStateButton_ = buttons->Add(new Button(i->T("Save State"), new LinearLayoutParams(0.0, G_VCENTER))); - saveStateButton_->OnClick.Handle(this, &SaveSlotView::OnSaveState); - - fv->OnClick.Handle(this, &SaveSlotView::OnScreenshotClick); - - if (SaveState::HasSaveInSlot(slot)) { - loadStateButton_ = buttons->Add(new Button(i->T("Load State"), new LinearLayoutParams(0.0, G_VCENTER))); - loadStateButton_->OnClick.Handle(this, &SaveSlotView::OnLoadState); - - std::string dateStr = SaveState::GetSlotDateAsString(slot_); - std::vector dateStrs; - SplitString(dateStr, ' ', dateStrs); - if (!dateStrs.empty() && !dateStrs[0].empty()) { - LinearLayout *strs = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - Add(strs); - for (size_t i = 0; i < dateStrs.size(); i++) { - strs->Add(new TextView(dateStrs[i], new LinearLayoutParams(0.0, G_VCENTER)))->SetShadow(true); - } - } - } else { - fv->SetFilename(""); - } - } + SaveSlotView(int slot, UI::LayoutParams *layoutParams = nullptr); void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { w = 500; h = 90; } - void Draw(UIContext &dc) { - if (g_Config.iCurrentStateSlot == slot_) { - dc.FillRect(UI::Drawable(0x70000000), GetBounds().Expand(3)); - dc.FillRect(UI::Drawable(0x70FFFFFF), GetBounds().Expand(3)); - } - UI::LinearLayout::Draw(dc); - } + void Draw(UIContext &dc) override; int GetSlot() const { return slot_; @@ -220,6 +176,53 @@ class SaveSlotView : public UI::LinearLayout { std::string screenshotFilename_; }; +SaveSlotView::SaveSlotView(int slot, UI::LayoutParams *layoutParams) : UI::LinearLayout(UI::ORIENT_HORIZONTAL, layoutParams), slot_(slot) { + using namespace UI; + + screenshotFilename_ = SaveState::GenerateSaveSlotFilename(slot, "jpg"); + PrioritizedWorkQueue *wq = g_gameInfoCache.WorkQueue(); + Add(new Spacer(5)); + + AsyncImageFileView *fv = Add(new AsyncImageFileView(screenshotFilename_, IS_DEFAULT, wq, new UI::LayoutParams(82 * 2, 47 * 2))); + fv->SetOverlayText(StringFromFormat("%i", slot_ + 1)); + + I18NCategory *i = GetI18NCategory("Pause"); + + LinearLayout *buttons = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + buttons->SetSpacing(2.0); + Add(buttons); + + saveStateButton_ = buttons->Add(new Button(i->T("Save State"), new LinearLayoutParams(0.0, G_VCENTER))); + saveStateButton_->OnClick.Handle(this, &SaveSlotView::OnSaveState); + + fv->OnClick.Handle(this, &SaveSlotView::OnScreenshotClick); + + if (SaveState::HasSaveInSlot(slot)) { + loadStateButton_ = buttons->Add(new Button(i->T("Load State"), new LinearLayoutParams(0.0, G_VCENTER))); + loadStateButton_->OnClick.Handle(this, &SaveSlotView::OnLoadState); + + std::string dateStr = SaveState::GetSlotDateAsString(slot_); + std::vector dateStrs; + SplitString(dateStr, ' ', dateStrs); + if (!dateStrs.empty() && !dateStrs[0].empty()) { + LinearLayout *strs = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + Add(strs); + for (size_t i = 0; i < dateStrs.size(); i++) { + strs->Add(new TextView(dateStrs[i], new LinearLayoutParams(0.0, G_VCENTER)))->SetShadow(true); + } + } + } else { + fv->SetFilename(""); + } +} + +void SaveSlotView::Draw(UIContext &dc) { + if (g_Config.iCurrentStateSlot == slot_) { + dc.FillRect(UI::Drawable(0x70000000), GetBounds().Expand(3)); + dc.FillRect(UI::Drawable(0x70FFFFFF), GetBounds().Expand(3)); + } + UI::LinearLayout::Draw(dc); +} UI::EventReturn SaveSlotView::OnLoadState(UI::EventParams &e) { g_Config.iCurrentStateSlot = slot_; diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp new file mode 100644 index 000000000000..128fab25e53e --- /dev/null +++ b/UI/SavedataScreen.cpp @@ -0,0 +1,364 @@ +// Copyright (c) 2013- PPSSPP Project. + +// This program 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, version 2.0 or later versions. + +// This program 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include + +#include "base/colorutil.h" +#include "base/timeutil.h" +#include "gfx_es2/draw_buffer.h" +#include "i18n/i18n.h" +#include "math/curves.h" +#include "util/text/utf8.h" +#include "ui/ui_context.h" +#include "ui/view.h" +#include "ui/viewgroup.h" +#include "UI/SavedataScreen.h" +#include "UI/MainScreen.h" +#include "UI/GameInfoCache.h" +#include "UI/ui_atlas.h" +#include "UI/PauseScreen.h" + +#include "Common/FileUtil.h" +#include "Core/Host.h" +#include "Core/Config.h" +#include "Core/SaveState.h" +#include "Core/System.h" + +class SavedataButton; + +std::string GetFileDateAsString(std::string filename) { + tm time; + if (File::GetModifTime(filename, time)) { + char buf[256]; + // TODO: Use local time format? Americans and some others might not like ISO standard :) + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time); + return std::string(buf); + } + return ""; +} + +class SavedataPopupScreen : public PopupScreen { +public: + SavedataPopupScreen(std::string savePath, std::string title) : PopupScreen(title), savePath_(savePath) { + } + + void CreatePopupContents(UI::ViewGroup *parent) override { + using namespace UI; + GameInfo *ginfo = g_gameInfoCache.GetInfo(screenManager()->getThin3DContext(), savePath_, GAMEINFO_WANTBG | GAMEINFO_WANTSIZE); + LinearLayout *root = new LinearLayout(ORIENT_VERTICAL); + parent->Add(root); + if (!ginfo) + return; + LinearLayout *toprow = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT)); + root->Add(toprow); + + I18NCategory *s = GetI18NCategory("Savedata"); + if (ginfo->fileType == FILETYPE_PSP_SAVEDATA_DIRECTORY) { + std::string savedata_detail = ginfo->paramSFO.GetValueString("SAVEDATA_DETAIL"); + std::string savedata_title = ginfo->paramSFO.GetValueString("SAVEDATA_TITLE"); + + if (ginfo->iconTexture) { + toprow->Add(new Thin3DTextureView(ginfo->iconTexture, IS_FIXED, new LinearLayoutParams(Margins(10, 5)))); + } + LinearLayout *topright = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); + topright->SetSpacing(1.0f); + topright->Add(new TextView(savedata_title, 0, false)); + topright->Add(new TextView(StringFromFormat("%d kB", ginfo->gameSize / 1024), 0, true)); + topright->Add(new TextView(GetFileDateAsString(savePath_ + "/PARAM.SFO"), 0, true)); + toprow->Add(topright); + root->Add(new Spacer(3.0)); + root->Add(new TextView(savedata_detail, 0, true, new LinearLayoutParams(Margins(10, 0)))); + root->Add(new Spacer(3.0)); + } else { + std::string image_path = ReplaceAll(savePath_, "ppst", "jpg"); + if (File::Exists(image_path)) { + PrioritizedWorkQueue *wq = g_gameInfoCache.WorkQueue(); + toprow->Add(new AsyncImageFileView(image_path, IS_DEFAULT, wq, new UI::LayoutParams(500, 500/16*9))); + } else { + toprow->Add(new TextView(s->T("No screenshot"), new LinearLayoutParams(Margins(10, 5)))); + } + root->Add(new TextView(GetFileDateAsString(savePath_), 0, true, new LinearLayoutParams(Margins(10, 5)))); + } + + I18NCategory *di = GetI18NCategory("Dialog"); + LinearLayout *buttons = new LinearLayout(ORIENT_HORIZONTAL); + buttons->Add(new Button(di->T("Back"), new LinearLayoutParams(1.0)))->OnClick.Handle(this, &UIScreen::OnBack); + buttons->Add(new Button(di->T("Delete"), new LinearLayoutParams(1.0)))->OnClick.Handle(this, &SavedataPopupScreen::OnDeleteButtonClick); + root->Add(buttons); + } + +private: + UI::EventReturn OnDeleteButtonClick(UI::EventParams &e); + std::string savePath_; +}; + +class SavedataButton : public UI::Clickable { +public: + SavedataButton(const std::string &gamePath, UI::LayoutParams *layoutParams = 0) + : UI::Clickable(layoutParams), savePath_(gamePath) {} + + void Draw(UIContext &dc) override; + void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { + w = 500; + h = 74; + } + + const std::string &GamePath() const { return savePath_; } + + std::string GetGamePath() const { return savePath_; } + +private: + std::string savePath_; + std::string title_; + std::string subtitle_; +}; + +UI::EventReturn SavedataPopupScreen::OnDeleteButtonClick(UI::EventParams &e) { + GameInfo *ginfo = g_gameInfoCache.GetInfo(nullptr, savePath_, GAMEINFO_WANTSIZE); + ginfo->Delete(); + screenManager()->finishDialog(this, DR_NO); + return UI::EVENT_DONE; +} + +static std::string CleanSaveString(std::string str) { + std::string s = ReplaceAll(str, "&", "&&"); + s = ReplaceAll(s, "\n", " "); + s = ReplaceAll(s, "\r", " "); + return s; +} + +void SavedataButton::Draw(UIContext &dc) { + GameInfo *ginfo = g_gameInfoCache.GetInfo(dc.GetThin3DContext(), savePath_, GAMEINFO_WANTSIZE); + Thin3DTexture *texture = 0; + u32 color = 0, shadowColor = 0; + using namespace UI; + + if (ginfo->iconTexture) { + texture = ginfo->iconTexture; + } + + int x = bounds_.x; + int y = bounds_.y; + int w = 144; + int h = bounds_.h; + + UI::Style style = dc.theme->itemStyle; + if (down_) + style = dc.theme->itemDownStyle; + + h = bounds_.h; + if (HasFocus()) + style = down_ ? dc.theme->itemDownStyle : dc.theme->itemFocusedStyle; + + Drawable bg = style.background; + + dc.Draw()->Flush(); + dc.RebindTexture(); + dc.FillRect(bg, bounds_); + dc.Draw()->Flush(); + + if (texture) { + color = whiteAlpha(ease((time_now_d() - ginfo->timeIconWasLoaded) * 2)); + shadowColor = blackAlpha(ease((time_now_d() - ginfo->timeIconWasLoaded) * 2)); + float tw = texture->Width(); + float th = texture->Height(); + + // Adjust position so we don't stretch the image vertically or horizontally. + // TODO: Add a param to specify fit? The below assumes it's never too wide. + float nw = h * tw / th; + x += (w - nw) / 2.0f; + w = nw; + } + + int txOffset = down_ ? 4 : 0; + txOffset = 0; + + Bounds overlayBounds = bounds_; + + // Render button + int dropsize = 10; + if (texture) { + if (txOffset) { + dropsize = 3; + y += txOffset * 2; + overlayBounds.y += txOffset * 2; + } + if (HasFocus()) { + dc.Draw()->Flush(); + dc.RebindTexture(); + float pulse = sinf(time_now() * 7.0f) * 0.25 + 0.8; + dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid, x - dropsize*1.5f, y - dropsize*1.5f, x + w + dropsize*1.5f, y + h + dropsize*1.5f, alphaMul(color, pulse), 1.0f); + dc.Draw()->Flush(); + } else { + dc.Draw()->Flush(); + dc.RebindTexture(); + dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid, x - dropsize, y - dropsize*0.5f, x + w + dropsize, y + h + dropsize*1.5, alphaMul(shadowColor, 0.5f), 1.0f); + dc.Draw()->Flush(); + } + } + + if (texture) { + dc.Draw()->Flush(); + dc.GetThin3DContext()->SetTexture(0, texture); + dc.Draw()->DrawTexRect(x, y, x + w, y + h, 0, 0, 1, 1, color); + dc.Draw()->Flush(); + } + + dc.Draw()->Flush(); + dc.RebindTexture(); + dc.SetFontStyle(dc.theme->uiFont); + + float tw, th; + dc.Draw()->Flush(); + dc.PushScissor(bounds_); + + if (title_.empty() && !ginfo->title.empty()) { + title_ = CleanSaveString(ginfo->title); + } + if (subtitle_.empty() && ginfo->gameSize > 0) { + std::string savedata_title = ginfo->paramSFO.GetValueString("SAVEDATA_TITLE"); + subtitle_ = CleanSaveString(savedata_title) + StringFromFormat(" (%d kB)", ginfo->gameSize / 1024); + } + + dc.MeasureText(dc.GetFontStyle(), title_.c_str(), &tw, &th, 0); + + int availableWidth = bounds_.w - 150; + float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f; + + float tx = 150; + if (availableWidth < tw) { + tx -= (1.0f + sin(time_now_d() * 1.5f)) * sineWidth; + Bounds tb = bounds_; + tb.x = bounds_.x + 150; + tb.w = bounds_.w - 150; + dc.PushScissor(tb); + } + dc.DrawText(title_.c_str(), bounds_.x + tx, bounds_.y + 4, style.fgColor, ALIGN_TOPLEFT); + dc.SetFontScale(0.6f, 0.6f); + dc.DrawText(subtitle_.c_str(), bounds_.x + tx, bounds_.y2() - 7, style.fgColor, ALIGN_BOTTOM); + dc.SetFontScale(1.0f, 1.0f); + + if (availableWidth < tw) { + dc.PopScissor(); + } + dc.Draw()->Flush(); + dc.PopScissor(); + + dc.RebindTexture(); +} + +SavedataBrowser::SavedataBrowser(std::string path, UI::LayoutParams *layoutParams) + : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), gameList_(0), path_(path) { + Refresh(); +} + +SavedataBrowser::~SavedataBrowser() { + g_gameInfoCache.PurgeType(FILETYPE_PPSSPP_SAVESTATE); + g_gameInfoCache.PurgeType(FILETYPE_PSP_SAVEDATA_DIRECTORY); +} + +void SavedataBrowser::Refresh() { + using namespace UI; + + // Kill all the contents + Clear(); + + Add(new Spacer(1.0f)); + I18NCategory *m = GetI18NCategory("MainMenu"); + + UI::LinearLayout *gl = new UI::LinearLayout(UI::ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + gl->SetSpacing(4.0f); + gameList_ = gl; + Add(gameList_); + + // Find games in the current directory and create new ones. + std::vector savedataButtons; + + std::vector fileInfo; + + getFilesInDir(path_.c_str(), &fileInfo, "ppst:"); + + for (size_t i = 0; i < fileInfo.size(); i++) { + bool isState = !fileInfo[i].isDirectory; + bool isSaveData = false; + + if (!isState && File::Exists(path_ + fileInfo[i].name + "/PARAM.SFO")) + isSaveData = true; + + if (isSaveData) { + savedataButtons.push_back(new SavedataButton(fileInfo[i].fullName, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT))); + } else if (isState) { + savedataButtons.push_back(new SavedataButton(fileInfo[i].fullName, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT))); + } + } + + for (size_t i = 0; i < savedataButtons.size(); i++) { + SavedataButton *b = gameList_->Add(savedataButtons[i]); + b->OnClick.Handle(this, &SavedataBrowser::SavedataButtonClick); + } +} + +UI::EventReturn SavedataBrowser::SavedataButtonClick(UI::EventParams &e) { + SavedataButton *button = static_cast(e.v); + UI::EventParams e2; + e2.s = button->GamePath(); + // Insta-update - here we know we are already on the right thread. + OnChoice.Trigger(e2); + return UI::EVENT_DONE; +} + +SavedataScreen::SavedataScreen(std::string gamePath) : UIDialogScreenWithGameBackground(gamePath) { +} + +void SavedataScreen::CreateViews() { + using namespace UI; + I18NCategory *s = GetI18NCategory("Savedata"); + I18NCategory *di = GetI18NCategory("Dialog"); + std::string savedata_dir = GetSysDirectory(DIRECTORY_SAVEDATA); + std::string savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE); + + gridStyle_ = false; + root_ = new LinearLayout(ORIENT_VERTICAL); + + TabHolder *tabs = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); + ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + browser_ = scroll->Add(new SavedataBrowser(savedata_dir, new LayoutParams(FILL_PARENT, FILL_PARENT))); + browser_->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); + + tabs->AddTab(s->T("Save Data"), scroll); + + ScrollView *scroll2 = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + SavedataBrowser *browser2 = scroll2->Add(new SavedataBrowser(savestate_dir)); + browser2->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); + tabs->AddTab(s->T("Save States"), scroll2); + + root_->Add(tabs); + root_->Add(new Button(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); +} + +UI::EventReturn SavedataScreen::OnSavedataButtonClick(UI::EventParams &e) { + GameInfo *ginfo = g_gameInfoCache.GetInfo(screenManager()->getThin3DContext(), e.s, 0); + screenManager()->push(new SavedataPopupScreen(e.s, ginfo->title)); + // the game path: e.s; + return UI::EVENT_DONE; +} + +void SavedataScreen::dialogFinished(const Screen *dialog, DialogResult result) { + if (result == DR_NO) { + RecreateViews(); + } +} diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h new file mode 100644 index 000000000000..44cb0a1c800d --- /dev/null +++ b/UI/SavedataScreen.h @@ -0,0 +1,56 @@ +// Copyright (c) 2015- PPSSPP Project. + +// This program 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, version 2.0 or later versions. + +// This program 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include + +#include "base/functional.h" +#include "ui/ui_screen.h" +#include "ui/view.h" +#include "ui/viewgroup.h" + +#include "UI/MiscScreens.h" + +class SavedataBrowser : public UI::LinearLayout { +public: + SavedataBrowser(std::string path, UI::LayoutParams *layoutParams = 0); + ~SavedataBrowser(); + + UI::Event OnChoice; + +private: + void Refresh(); + UI::EventReturn SavedataButtonClick(UI::EventParams &e); + + std::string path_; + UI::ViewGroup *gameList_; +}; + +class SavedataScreen : public UIDialogScreenWithGameBackground { +public: + // gamePath can be empty, in that case this screen will show all savedata in the save directory. + SavedataScreen(std::string gamePath); + + void dialogFinished(const Screen *dialog, DialogResult result) override; + +protected: + UI::EventReturn OnSavedataButtonClick(UI::EventParams &e); + void CreateViews() override; + bool gridStyle_; + SavedataBrowser *browser_; +}; diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 4d854ee62df0..d9fccb6c3d34 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -34,6 +34,7 @@ + @@ -57,6 +58,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 38a4fcb250bc..84115f0088a9 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -53,6 +53,9 @@ Screens + + Screens + @@ -106,6 +109,9 @@ Screens + + Screens + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 0f401484d977..808519f1fe88 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -336,6 +336,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/MiscScreens.cpp \ $(SRC)/UI/ReportScreen.cpp \ $(SRC)/UI/PauseScreen.cpp \ + $(SRC)/UI/SavedataScreen.cpp \ $(SRC)/UI/Store.cpp \ $(SRC)/UI/GamepadEmu.cpp \ $(SRC)/UI/GameInfoCache.cpp \ diff --git a/native b/native index 316d6ab84def..ba829ad4aa7a 160000 --- a/native +++ b/native @@ -1 +1 @@ -Subproject commit 316d6ab84def4666fe13ff0c14c9bcb1659ab5c1 +Subproject commit ba829ad4aa7a4acbacbaa7f77233e037862443e4