mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-27 07:20:49 +00:00
1372 lines
43 KiB
C++
1372 lines
43 KiB
C++
// 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 <cmath>
|
|
#include <algorithm>
|
|
|
|
#include "ppsspp_config.h"
|
|
#include "base/colorutil.h"
|
|
#include "base/display.h"
|
|
#include "base/timeutil.h"
|
|
#include "file/path.h"
|
|
#include "gfx_es2/draw_buffer.h"
|
|
#include "math/curves.h"
|
|
#include "base/stringutil.h"
|
|
#include "ui/ui_context.h"
|
|
#include "ui/view.h"
|
|
#include "ui/viewgroup.h"
|
|
#include "util/text/utf8.h"
|
|
|
|
#include "Common/FileUtil.h"
|
|
#include "Core/System.h"
|
|
#include "Core/Host.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/Util/GameManager.h"
|
|
|
|
#include "UI/BackgroundAudio.h"
|
|
#include "UI/EmuScreen.h"
|
|
#include "UI/MainScreen.h"
|
|
#include "UI/GameScreen.h"
|
|
#include "UI/GameInfoCache.h"
|
|
#include "UI/GameSettingsScreen.h"
|
|
#include "UI/MiscScreens.h"
|
|
#include "UI/ControlMappingScreen.h"
|
|
#include "UI/DisplayLayoutScreen.h"
|
|
#include "UI/SavedataScreen.h"
|
|
#include "UI/Store.h"
|
|
#include "UI/ui_atlas.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/Loaders.h"
|
|
#include "GPU/GPUInterface.h"
|
|
#include "i18n/i18n.h"
|
|
|
|
#include "Core/HLE/sceDisplay.h"
|
|
#include "Core/HLE/sceUmd.h"
|
|
|
|
#ifdef _WIN32
|
|
// Unfortunate, for undef DrawText...
|
|
#include "Common/CommonWindows.h"
|
|
#endif
|
|
|
|
#ifdef ANDROID_NDK_PROFILER
|
|
#include <stdlib.h>
|
|
#include "android/android-ndk-profiler/prof.h"
|
|
#endif
|
|
|
|
#include <sstream>
|
|
|
|
bool MainScreen::showHomebrewTab = false;
|
|
|
|
static bool IsTempPath(const std::string &str) {
|
|
std::string item = str;
|
|
|
|
const auto testPath = [&](std::string temp) {
|
|
#ifdef _WIN32
|
|
temp = ReplaceAll(temp, "/", "\\");
|
|
if (!temp.empty() && temp[temp.size() - 1] != '\\')
|
|
temp += "\\";
|
|
#else
|
|
if (!temp.empty() && temp[temp.size() - 1] != '/')
|
|
temp += "/";
|
|
#endif
|
|
return startsWith(item, temp);
|
|
};
|
|
|
|
const auto testCPath = [&](const char *temp) {
|
|
if (temp && temp[0])
|
|
return testPath(temp);
|
|
return false;
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
// Normalize slashes.
|
|
item = ReplaceAll(str, "/", "\\");
|
|
|
|
std::wstring tempPath(MAX_PATH, '\0');
|
|
size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
|
|
if (sz >= tempPath.size()) {
|
|
tempPath.resize(sz);
|
|
sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
|
|
}
|
|
// Need to resize off the null terminator either way.
|
|
tempPath.resize(sz);
|
|
if (testPath(ConvertWStringToUTF8(tempPath)))
|
|
return true;
|
|
#endif
|
|
|
|
if (testCPath(getenv("TMPDIR")))
|
|
return true;
|
|
if (testCPath(getenv("TMP")))
|
|
return true;
|
|
if (testCPath(getenv("TEMP")))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
class GameButton : public UI::Clickable {
|
|
public:
|
|
GameButton(const std::string &gamePath, bool gridStyle, UI::LayoutParams *layoutParams = 0)
|
|
: UI::Clickable(layoutParams), gridStyle_(gridStyle), gamePath_(gamePath) {}
|
|
|
|
void Draw(UIContext &dc) override;
|
|
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
|
|
if (gridStyle_) {
|
|
w = 144;
|
|
h = 80;
|
|
} else {
|
|
w = 500;
|
|
h = 50;
|
|
}
|
|
}
|
|
|
|
const std::string &GamePath() const { return gamePath_; }
|
|
|
|
void SetHoldEnabled(bool hold) {
|
|
holdEnabled_ = hold;
|
|
}
|
|
void Touch(const TouchInput &input) override {
|
|
UI::Clickable::Touch(input);
|
|
hovering_ = bounds_.Contains(input.x, input.y);
|
|
if (hovering_ && (input.flags & TOUCH_DOWN)) {
|
|
holdStart_ = time_now_d();
|
|
}
|
|
if (input.flags & TOUCH_UP) {
|
|
holdStart_ = 0;
|
|
}
|
|
}
|
|
|
|
bool Key(const KeyInput &key) override {
|
|
std::vector<int> pspKeys;
|
|
bool showInfo = false;
|
|
|
|
if (KeyMap::KeyToPspButton(key.deviceId, key.keyCode, &pspKeys)) {
|
|
for (auto it = pspKeys.begin(), end = pspKeys.end(); it != end; ++it) {
|
|
// If the button mapped to triangle, then show the info.
|
|
if (HasFocus() && (key.flags & KEY_UP) && *it == CTRL_TRIANGLE) {
|
|
showInfo = true;
|
|
}
|
|
}
|
|
} else if (hovering_ && key.deviceId == DEVICE_ID_MOUSE && key.keyCode == NKCODE_EXT_MOUSEBUTTON_2) {
|
|
// If it's the right mouse button, and it's not otherwise mapped, show the info also.
|
|
if (key.flags & KEY_DOWN) {
|
|
showInfoPressed_ = true;
|
|
}
|
|
if ((key.flags & KEY_UP) && showInfoPressed_) {
|
|
showInfo = true;
|
|
showInfoPressed_ = false;
|
|
}
|
|
}
|
|
|
|
if (showInfo) {
|
|
TriggerOnHoldClick();
|
|
return true;
|
|
}
|
|
|
|
return Clickable::Key(key);
|
|
}
|
|
|
|
void Update() override {
|
|
// Hold button for 1.5 seconds to launch the game options
|
|
if (holdEnabled_ && holdStart_ != 0.0 && holdStart_ < time_now_d() - 1.5) {
|
|
TriggerOnHoldClick();
|
|
}
|
|
}
|
|
|
|
void FocusChanged(int focusFlags) override {
|
|
UI::Clickable::FocusChanged(focusFlags);
|
|
TriggerOnHighlight(focusFlags);
|
|
}
|
|
|
|
UI::Event OnHoldClick;
|
|
UI::Event OnHighlight;
|
|
|
|
private:
|
|
void TriggerOnHoldClick() {
|
|
holdStart_ = 0.0;
|
|
UI::EventParams e{};
|
|
e.v = this;
|
|
e.s = gamePath_;
|
|
down_ = false;
|
|
OnHoldClick.Trigger(e);
|
|
}
|
|
void TriggerOnHighlight(int focusFlags) {
|
|
UI::EventParams e{};
|
|
e.v = this;
|
|
e.s = gamePath_;
|
|
e.a = focusFlags;
|
|
OnHighlight.Trigger(e);
|
|
}
|
|
|
|
bool gridStyle_;
|
|
std::string gamePath_;
|
|
std::string title_;
|
|
|
|
double holdStart_ = 0.0;
|
|
bool holdEnabled_ = true;
|
|
bool showInfoPressed_ = false;
|
|
bool hovering_ = false;
|
|
};
|
|
|
|
void GameButton::Draw(UIContext &dc) {
|
|
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, 0);
|
|
Draw::Texture *texture = 0;
|
|
u32 color = 0, shadowColor = 0;
|
|
using namespace UI;
|
|
|
|
if (ginfo->icon.texture) {
|
|
texture = ginfo->icon.texture->GetTexture();
|
|
}
|
|
|
|
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;
|
|
|
|
if (!gridStyle_ || !texture) {
|
|
h = 50;
|
|
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->icon.timeLoaded) * 2));
|
|
shadowColor = blackAlpha(ease((time_now_d() - ginfo->icon.timeLoaded) * 2));
|
|
float tw = texture->Width();
|
|
float th = texture->Height();
|
|
|
|
// Adjust position so we don't stretch the image vertically or horizontally.
|
|
// Make sure it's not wider than 144 (like Doom Legacy homebrew), ugly in the grid mode.
|
|
float nw = std::min(h * tw / th, 144.0f);
|
|
x += (w - nw) / 2.0f;
|
|
w = nw;
|
|
}
|
|
|
|
int txOffset = down_ ? 4 : 0;
|
|
if (!gridStyle_) txOffset = 0;
|
|
|
|
Bounds overlayBounds = bounds_;
|
|
u32 overlayColor = 0;
|
|
if (holdEnabled_ && holdStart_ != 0.0) {
|
|
double time_held = time_now_d() - holdStart_;
|
|
overlayColor = whiteAlpha(time_held / 2.5f);
|
|
}
|
|
|
|
// Render button
|
|
int dropsize = 10;
|
|
if (texture) {
|
|
if (!gridStyle_) {
|
|
x += 4;
|
|
}
|
|
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();
|
|
}
|
|
|
|
dc.Draw()->Flush();
|
|
dc.GetDrawContext()->BindTexture(0, texture);
|
|
if (holdStart_ != 0.0) {
|
|
double time_held = time_now_d() - holdStart_;
|
|
int holdFrameCount = (int)(time_held * 60.0f);
|
|
if (holdFrameCount > 60) {
|
|
// Blink before launching by holding
|
|
if (((holdFrameCount >> 3) & 1) == 0)
|
|
color = darkenColor(color);
|
|
}
|
|
}
|
|
dc.Draw()->DrawTexRect(x, y, x+w, y+h, 0, 0, 1, 1, color);
|
|
dc.Draw()->Flush();
|
|
}
|
|
|
|
char discNumInfo[8];
|
|
if (ginfo->disc_total > 1)
|
|
sprintf(discNumInfo, "-DISC%d", ginfo->disc_number);
|
|
else
|
|
strcpy(discNumInfo, "");
|
|
|
|
dc.Draw()->Flush();
|
|
dc.RebindTexture();
|
|
dc.SetFontStyle(dc.theme->uiFont);
|
|
if (!gridStyle_) {
|
|
float tw, th;
|
|
dc.Draw()->Flush();
|
|
dc.PushScissor(bounds_);
|
|
const std::string currentTitle = ginfo->GetTitle();
|
|
if (!currentTitle.empty()) {
|
|
title_ = ReplaceAll(currentTitle + discNumInfo, "&", "&&");
|
|
title_ = ReplaceAll(title_, "\n", " ");
|
|
}
|
|
|
|
dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, title_.c_str(), &tw, &th, 0);
|
|
|
|
int availableWidth = bounds_.w - 150;
|
|
if (g_Config.bShowIDOnGameIcon) {
|
|
float vw, vh;
|
|
dc.MeasureText(dc.GetFontStyle(), 0.7f, 0.7f, ginfo->id_version.c_str(), &vw, &vh, 0);
|
|
availableWidth -= vw + 20;
|
|
dc.SetFontScale(0.7f, 0.7f);
|
|
dc.DrawText(ginfo->id_version.c_str(), availableWidth + 160, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
|
|
dc.SetFontScale(1.0f, 1.0f);
|
|
}
|
|
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 = availableWidth;
|
|
dc.PushScissor(tb);
|
|
}
|
|
dc.DrawText(title_.c_str(), bounds_.x + tx, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
|
|
if (availableWidth < tw) {
|
|
dc.PopScissor();
|
|
}
|
|
dc.Draw()->Flush();
|
|
dc.PopScissor();
|
|
} else if (!texture) {
|
|
dc.Draw()->Flush();
|
|
dc.PushScissor(bounds_);
|
|
dc.DrawText(title_.c_str(), bounds_.x + 4, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
|
|
dc.Draw()->Flush();
|
|
dc.PopScissor();
|
|
} else {
|
|
dc.Draw()->Flush();
|
|
}
|
|
if (ginfo->hasConfig && !ginfo->id.empty()) {
|
|
if (gridStyle_) {
|
|
dc.Draw()->DrawImage(I_GEAR, x, y + h - ui_images[I_GEAR].h, 1.0f);
|
|
} else {
|
|
dc.Draw()->DrawImage(I_GEAR, x - ui_images[I_GEAR].w, y, 1.0f);
|
|
}
|
|
}
|
|
if (g_Config.bShowRegionOnGameIcon && ginfo->region >= 0 && ginfo->region < GAMEREGION_MAX && ginfo->region != GAMEREGION_OTHER) {
|
|
static const int regionIcons[GAMEREGION_MAX] = {
|
|
I_FLAG_JP,
|
|
I_FLAG_US,
|
|
I_FLAG_EU,
|
|
I_FLAG_HK,
|
|
I_FLAG_AS,
|
|
I_FLAG_KO
|
|
};
|
|
if (gridStyle_) {
|
|
dc.Draw()->DrawImage(regionIcons[ginfo->region], x + w - ui_images[regionIcons[ginfo->region]].w - 5, y + h - ui_images[regionIcons[ginfo->region]].h - 5, 1.0f);
|
|
} else {
|
|
dc.Draw()->DrawImage(regionIcons[ginfo->region], x - 2 - ui_images[regionIcons[ginfo->region]].w - 3, y + h - ui_images[regionIcons[ginfo->region]].h - 5, 1.0f);
|
|
}
|
|
}
|
|
if (gridStyle_ && g_Config.bShowIDOnGameIcon) {
|
|
dc.SetFontScale(0.5f, 0.5f);
|
|
dc.DrawText(ginfo->id_version.c_str(), x+5, y+1, 0xFF000000, ALIGN_TOPLEFT);
|
|
dc.DrawText(ginfo->id_version.c_str(), x+4, y, 0xFFffFFff, ALIGN_TOPLEFT);
|
|
dc.SetFontScale(1.0f, 1.0f);
|
|
}
|
|
if (overlayColor) {
|
|
dc.FillRect(Drawable(overlayColor), overlayBounds);
|
|
}
|
|
dc.RebindTexture();
|
|
}
|
|
|
|
class DirButton : public UI::Button {
|
|
public:
|
|
DirButton(const std::string &path, UI::LayoutParams *layoutParams)
|
|
: UI::Button(path, layoutParams), path_(path), absolute_(false) {}
|
|
DirButton(const std::string &path, const std::string &text, UI::LayoutParams *layoutParams = 0)
|
|
: UI::Button(text, layoutParams), path_(path), absolute_(true) {}
|
|
|
|
virtual void Draw(UIContext &dc);
|
|
|
|
const std::string GetPath() const {
|
|
return path_;
|
|
}
|
|
|
|
bool PathAbsolute() const {
|
|
return absolute_;
|
|
}
|
|
|
|
private:
|
|
std::string path_;
|
|
bool absolute_;
|
|
};
|
|
|
|
void DirButton::Draw(UIContext &dc) {
|
|
using namespace UI;
|
|
Style style = dc.theme->buttonStyle;
|
|
|
|
if (HasFocus()) style = dc.theme->buttonFocusedStyle;
|
|
if (down_) style = dc.theme->buttonDownStyle;
|
|
if (!IsEnabled()) style = dc.theme->buttonDisabledStyle;
|
|
|
|
dc.FillRect(style.background, bounds_);
|
|
|
|
const std::string text = GetText();
|
|
|
|
int image = I_FOLDER;
|
|
if (text == "..") {
|
|
image = I_UP_DIRECTORY;
|
|
}
|
|
|
|
float tw, th;
|
|
dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, text.c_str(), &tw, &th, 0);
|
|
|
|
bool compact = bounds_.w < 180;
|
|
|
|
if (compact) {
|
|
// No icon, except "up"
|
|
dc.PushScissor(bounds_);
|
|
if (image == I_FOLDER) {
|
|
dc.DrawText(text.c_str(), bounds_.x + 5, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
|
|
} else {
|
|
dc.Draw()->DrawImage(image, bounds_.centerX(), bounds_.centerY(), 1.0f, 0xFFFFFFFF, ALIGN_CENTER);
|
|
}
|
|
dc.PopScissor();
|
|
} else {
|
|
bool scissor = false;
|
|
if (tw + 150 > bounds_.w) {
|
|
dc.PushScissor(bounds_);
|
|
scissor = true;
|
|
}
|
|
|
|
dc.Draw()->DrawImage(image, bounds_.x + 72, bounds_.centerY(), .88f, 0xFFFFFFFF, ALIGN_CENTER);
|
|
dc.DrawText(text.c_str(), bounds_.x + 150, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
|
|
|
|
if (scissor) {
|
|
dc.PopScissor();
|
|
}
|
|
}
|
|
}
|
|
|
|
GameBrowser::GameBrowser(std::string path, BrowseFlags browseFlags, bool *gridStyle, std::string lastText, std::string lastLink, UI::LayoutParams *layoutParams)
|
|
: LinearLayout(UI::ORIENT_VERTICAL, layoutParams), path_(path), gridStyle_(gridStyle), browseFlags_(browseFlags), lastText_(lastText), lastLink_(lastLink) {
|
|
using namespace UI;
|
|
Refresh();
|
|
}
|
|
|
|
void GameBrowser::FocusGame(const std::string &gamePath) {
|
|
focusGamePath_ = gamePath;
|
|
Refresh();
|
|
focusGamePath_.clear();
|
|
}
|
|
|
|
void GameBrowser::SetPath(const std::string &path) {
|
|
path_.SetPath(path);
|
|
g_Config.currentDirectory = path_.GetPath();
|
|
Refresh();
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::LayoutChange(UI::EventParams &e) {
|
|
*gridStyle_ = e.a == 0 ? true : false;
|
|
Refresh();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::LastClick(UI::EventParams &e) {
|
|
LaunchBrowser(lastLink_.c_str());
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::HomeClick(UI::EventParams &e) {
|
|
#ifdef __ANDROID__
|
|
SetPath(g_Config.memStickDirectory);
|
|
#elif defined(USING_QT_UI) || defined(USING_WIN_UI)
|
|
if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
|
|
System_SendMessage("browse_folder", "");
|
|
}
|
|
#elif PPSSPP_PLATFORM(UWP)
|
|
// TODO UWP
|
|
SetPath(g_Config.memStickDirectory);
|
|
#else
|
|
SetPath(getenv("HOME"));
|
|
#endif
|
|
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::PinToggleClick(UI::EventParams &e) {
|
|
auto &pinnedPaths = g_Config.vPinnedPaths;
|
|
const std::string path = File::ResolvePath(path_.GetPath());
|
|
if (IsCurrentPathPinned()) {
|
|
pinnedPaths.erase(std::remove(pinnedPaths.begin(), pinnedPaths.end(), path), pinnedPaths.end());
|
|
} else {
|
|
pinnedPaths.push_back(path);
|
|
}
|
|
Refresh();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
bool GameBrowser::DisplayTopBar() {
|
|
return path_.GetPath() != "!RECENT";
|
|
}
|
|
|
|
bool GameBrowser::HasSpecialFiles(std::vector<std::string> &filenames) {
|
|
if (path_.GetPath() == "!RECENT") {
|
|
filenames = g_Config.recentIsos;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void GameBrowser::Update() {
|
|
LinearLayout::Update();
|
|
if (listingPending_ && path_.IsListingReady()) {
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
void GameBrowser::Refresh() {
|
|
using namespace UI;
|
|
|
|
homebrewStoreButton_ = nullptr;
|
|
// Kill all the contents
|
|
Clear();
|
|
|
|
Add(new Spacer(1.0f));
|
|
auto mm = GetI18NCategory("MainMenu");
|
|
|
|
// No topbar on recent screen
|
|
if (DisplayTopBar()) {
|
|
LinearLayout *topBar = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
if (browseFlags_ & BrowseFlags::NAVIGATE) {
|
|
topBar->Add(new Spacer(2.0f));
|
|
topBar->Add(new TextView(path_.GetFriendlyPath().c_str(), ALIGN_VCENTER | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f)));
|
|
if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
|
|
topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick);
|
|
} else {
|
|
topBar->Add(new Choice(mm->T("Home"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick);
|
|
}
|
|
} else {
|
|
topBar->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f)));
|
|
}
|
|
|
|
ChoiceStrip *layoutChoice = topBar->Add(new ChoiceStrip(ORIENT_HORIZONTAL));
|
|
layoutChoice->AddChoice(I_GRID);
|
|
layoutChoice->AddChoice(I_LINES);
|
|
layoutChoice->SetSelection(*gridStyle_ ? 0 : 1);
|
|
layoutChoice->OnChoice.Handle(this, &GameBrowser::LayoutChange);
|
|
Add(topBar);
|
|
}
|
|
|
|
if (*gridStyle_) {
|
|
gameList_ = new UI::GridLayout(UI::GridLayoutSettings(150, 85), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
} else {
|
|
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<DirButton *> dirButtons;
|
|
std::vector<GameButton *> gameButtons;
|
|
|
|
listingPending_ = !path_.IsListingReady();
|
|
|
|
std::vector<std::string> filenames;
|
|
if (HasSpecialFiles(filenames)) {
|
|
for (size_t i = 0; i < filenames.size(); i++) {
|
|
gameButtons.push_back(new GameButton(filenames[i], *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT)));
|
|
}
|
|
} else if (!listingPending_) {
|
|
std::vector<FileInfo> fileInfo;
|
|
path_.GetListing(fileInfo, "iso:cso:pbp:elf:prx:ppdmp:");
|
|
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 && !isSaveData) {
|
|
if (browseFlags_ & BrowseFlags::NAVIGATE) {
|
|
dirButtons.push_back(new DirButton(fileInfo[i].fullName, fileInfo[i].name, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
|
|
}
|
|
} else {
|
|
gameButtons.push_back(new GameButton(fileInfo[i].fullName, *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT)));
|
|
}
|
|
}
|
|
// Put RAR/ZIP files at the end to get them out of the way. They're only shown so that people
|
|
// can click them and get an explanation that they need to unpack them. This is necessary due
|
|
// to a flood of support email...
|
|
if (browseFlags_ & BrowseFlags::ARCHIVES) {
|
|
fileInfo.clear();
|
|
path_.GetListing(fileInfo, "zip:rar:r01:7z:");
|
|
if (!fileInfo.empty()) {
|
|
UI::LinearLayout *zl = new UI::LinearLayout(UI::ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
zl->SetSpacing(4.0f);
|
|
Add(zl);
|
|
for (size_t i = 0; i < fileInfo.size(); i++) {
|
|
if (!fileInfo[i].isDirectory) {
|
|
GameButton *b = zl->Add(new GameButton(fileInfo[i].fullName, false, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT)));
|
|
b->OnClick.Handle(this, &GameBrowser::GameButtonClick);
|
|
b->SetHoldEnabled(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (browseFlags_ & BrowseFlags::NAVIGATE) {
|
|
gameList_->Add(new DirButton("..", new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)))->
|
|
OnClick.Handle(this, &GameBrowser::NavigateClick);
|
|
|
|
// Add any pinned paths before other directories.
|
|
auto pinnedPaths = GetPinnedPaths();
|
|
for (auto it = pinnedPaths.begin(), end = pinnedPaths.end(); it != end; ++it) {
|
|
gameList_->Add(new DirButton(*it, GetBaseName(*it), new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)))->
|
|
OnClick.Handle(this, &GameBrowser::NavigateClick);
|
|
}
|
|
}
|
|
|
|
if (listingPending_) {
|
|
gameList_->Add(new UI::TextView(mm->T("Loading..."), ALIGN_CENTER, false, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
|
|
}
|
|
|
|
for (size_t i = 0; i < dirButtons.size(); i++) {
|
|
gameList_->Add(dirButtons[i])->OnClick.Handle(this, &GameBrowser::NavigateClick);
|
|
}
|
|
|
|
for (size_t i = 0; i < gameButtons.size(); i++) {
|
|
GameButton *b = gameList_->Add(gameButtons[i]);
|
|
b->OnClick.Handle(this, &GameBrowser::GameButtonClick);
|
|
b->OnHoldClick.Handle(this, &GameBrowser::GameButtonHoldClick);
|
|
b->OnHighlight.Handle(this, &GameBrowser::GameButtonHighlight);
|
|
|
|
if (!focusGamePath_.empty() && b->GamePath() == focusGamePath_) {
|
|
b->SetFocus();
|
|
}
|
|
}
|
|
|
|
// Show a button to toggle pinning at the very end.
|
|
if (browseFlags_ & BrowseFlags::PIN) {
|
|
std::string caption = IsCurrentPathPinned() ? "-" : "+";
|
|
if (!*gridStyle_) {
|
|
caption = IsCurrentPathPinned() ? mm->T("UnpinPath", "Unpin") : mm->T("PinPath", "Pin");
|
|
}
|
|
gameList_->Add(new UI::Button(caption, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)))->
|
|
OnClick.Handle(this, &GameBrowser::PinToggleClick);
|
|
}
|
|
|
|
if (browseFlags_ & BrowseFlags::HOMEBREW_STORE) {
|
|
Add(new Spacer());
|
|
homebrewStoreButton_ = Add(new Choice(mm->T("DownloadFromStore", "Download from the PPSSPP Homebrew Store"), new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT)));
|
|
} else {
|
|
homebrewStoreButton_ = nullptr;
|
|
}
|
|
|
|
if (!lastText_.empty() && gameButtons.empty()) {
|
|
Add(new Spacer());
|
|
Add(new Choice(lastText_, new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT)))->OnClick.Handle(this, &GameBrowser::LastClick);
|
|
}
|
|
}
|
|
|
|
bool GameBrowser::IsCurrentPathPinned() {
|
|
const auto paths = g_Config.vPinnedPaths;
|
|
return std::find(paths.begin(), paths.end(), File::ResolvePath(path_.GetPath())) != paths.end();
|
|
}
|
|
|
|
const std::vector<std::string> GameBrowser::GetPinnedPaths() {
|
|
#ifndef _WIN32
|
|
static const std::string sepChars = "/";
|
|
#else
|
|
static const std::string sepChars = "/\\";
|
|
#endif
|
|
|
|
const std::string currentPath = File::ResolvePath(path_.GetPath());
|
|
const std::vector<std::string> paths = g_Config.vPinnedPaths;
|
|
std::vector<std::string> results;
|
|
for (size_t i = 0; i < paths.size(); ++i) {
|
|
// We want to exclude the current path, and its direct children.
|
|
if (paths[i] == currentPath) {
|
|
continue;
|
|
}
|
|
if (startsWith(paths[i], currentPath)) {
|
|
std::string descendant = paths[i].substr(currentPath.size());
|
|
// If there's only one separator (or none), its a direct child.
|
|
if (descendant.find_last_of(sepChars) == descendant.find_first_of(sepChars)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
results.push_back(paths[i]);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
const std::string GameBrowser::GetBaseName(const std::string &path) {
|
|
#ifndef _WIN32
|
|
static const std::string sepChars = "/";
|
|
#else
|
|
static const std::string sepChars = "/\\";
|
|
#endif
|
|
|
|
auto trailing = path.find_last_not_of(sepChars);
|
|
if (trailing != path.npos) {
|
|
size_t start = path.find_last_of(sepChars, trailing);
|
|
if (start != path.npos) {
|
|
return path.substr(start + 1, trailing - start);
|
|
}
|
|
return path.substr(0, trailing);
|
|
}
|
|
|
|
size_t start = path.find_last_of(sepChars);
|
|
if (start != path.npos) {
|
|
return path.substr(start + 1);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::GameButtonClick(UI::EventParams &e) {
|
|
GameButton *button = static_cast<GameButton *>(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;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::GameButtonHoldClick(UI::EventParams &e) {
|
|
GameButton *button = static_cast<GameButton *>(e.v);
|
|
UI::EventParams e2{};
|
|
e2.s = button->GamePath();
|
|
// Insta-update - here we know we are already on the right thread.
|
|
OnHoldChoice.Trigger(e2);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::GameButtonHighlight(UI::EventParams &e) {
|
|
// Insta-update - here we know we are already on the right thread.
|
|
OnHighlight.Trigger(e);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn GameBrowser::NavigateClick(UI::EventParams &e) {
|
|
DirButton *button = static_cast<DirButton *>(e.v);
|
|
std::string text = button->GetPath();
|
|
if (button->PathAbsolute()) {
|
|
path_.SetPath(text);
|
|
} else {
|
|
path_.Navigate(text);
|
|
}
|
|
g_Config.currentDirectory = path_.GetPath();
|
|
Refresh();
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
MainScreen::MainScreen() : highlightProgress_(0.0f), prevHighlightProgress_(0.0f), backFromStore_(false), lockBackgroundAudio_(false) {
|
|
System_SendMessage("event", "mainscreen");
|
|
SetBackgroundAudioGame("");
|
|
lastVertical_ = UseVerticalLayout();
|
|
}
|
|
|
|
MainScreen::~MainScreen() {
|
|
SetBackgroundAudioGame("");
|
|
}
|
|
|
|
|
|
void MainScreen::CreateViews() {
|
|
// Information in the top left.
|
|
// Back button to the bottom left.
|
|
// Scrolling action menu to the right.
|
|
using namespace UI;
|
|
|
|
bool vertical = UseVerticalLayout();
|
|
|
|
auto mm = GetI18NCategory("MainMenu");
|
|
|
|
Margins actionMenuMargins(0, 10, 10, 0);
|
|
|
|
tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
|
|
ViewGroup *leftColumn = tabHolder_;
|
|
tabHolder_->SetTag("MainScreenGames");
|
|
gameBrowsers_.clear();
|
|
|
|
tabHolder_->SetClip(true);
|
|
|
|
bool showRecent = g_Config.iMaxRecent > 0;
|
|
bool hasStorageAccess = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE) == PERMISSION_STATUS_GRANTED;
|
|
bool storageIsTemporary = IsTempPath(GetSysDirectory(DIRECTORY_SAVEDATA)) && !confirmedTemporary_;
|
|
if (showRecent && !hasStorageAccess) {
|
|
showRecent = !g_Config.recentIsos.empty();
|
|
}
|
|
|
|
if (showRecent) {
|
|
ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
scrollRecentGames->SetTag("MainScreenRecentGames");
|
|
GameBrowser *tabRecentGames = new GameBrowser(
|
|
"!RECENT", BrowseFlags::NONE, &g_Config.bGridView1, "", "",
|
|
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
|
|
scrollRecentGames->Add(tabRecentGames);
|
|
gameBrowsers_.push_back(tabRecentGames);
|
|
|
|
tabHolder_->AddTab(mm->T("Recent"), scrollRecentGames);
|
|
tabRecentGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
|
|
tabRecentGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
|
|
tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
|
|
}
|
|
|
|
Button *focusButton = nullptr;
|
|
if (hasStorageAccess) {
|
|
ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
scrollAllGames->SetTag("MainScreenAllGames");
|
|
ScrollView *scrollHomebrew = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
scrollHomebrew->SetTag("MainScreenHomebrew");
|
|
|
|
GameBrowser *tabAllGames = new GameBrowser(g_Config.currentDirectory, BrowseFlags::STANDARD, &g_Config.bGridView2,
|
|
mm->T("How to get games"), "https://www.ppsspp.org/getgames.html",
|
|
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
|
|
GameBrowser *tabHomebrew = new GameBrowser(GetSysDirectory(DIRECTORY_GAME), BrowseFlags::HOMEBREW_STORE, &g_Config.bGridView3,
|
|
mm->T("How to get homebrew & demos", "How to get homebrew && demos"), "https://www.ppsspp.org/gethomebrew.html",
|
|
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
|
|
|
|
Choice *hbStore = tabHomebrew->HomebrewStoreButton();
|
|
if (hbStore) {
|
|
hbStore->OnClick.Handle(this, &MainScreen::OnHomebrewStore);
|
|
}
|
|
|
|
scrollAllGames->Add(tabAllGames);
|
|
gameBrowsers_.push_back(tabAllGames);
|
|
scrollHomebrew->Add(tabHomebrew);
|
|
gameBrowsers_.push_back(tabHomebrew);
|
|
|
|
tabHolder_->AddTab(mm->T("Games"), scrollAllGames);
|
|
tabHolder_->AddTab(mm->T("Homebrew & Demos"), scrollHomebrew);
|
|
|
|
tabAllGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
|
|
tabHomebrew->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant);
|
|
|
|
tabAllGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
|
|
tabHomebrew->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected);
|
|
|
|
tabAllGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
|
|
tabHomebrew->OnHighlight.Handle(this, &MainScreen::OnGameHighlight);
|
|
|
|
if (g_Config.recentIsos.size() > 0) {
|
|
tabHolder_->SetCurrentTab(0, true);
|
|
} else if (g_Config.iMaxRecent > 0) {
|
|
tabHolder_->SetCurrentTab(1, true);
|
|
}
|
|
|
|
if (backFromStore_ || showHomebrewTab) {
|
|
tabHolder_->SetCurrentTab(2, true);
|
|
backFromStore_ = false;
|
|
showHomebrewTab = false;
|
|
}
|
|
|
|
if (storageIsTemporary) {
|
|
LinearLayout *buttonHolder = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
|
buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f)));
|
|
focusButton = new Button(mm->T("SavesAreTemporaryIgnore", "Ignore warning"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
|
focusButton->SetPadding(32, 16);
|
|
buttonHolder->Add(focusButton)->OnClick.Add([this](UI::EventParams &e) {
|
|
confirmedTemporary_ = true;
|
|
RecreateViews();
|
|
return UI::EVENT_DONE;
|
|
});
|
|
buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f)));
|
|
|
|
leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f)));
|
|
leftColumn->Add(new TextView(mm->T("SavesAreTemporary", "PPSSPP saving in temporary storage"), ALIGN_HCENTER, false));
|
|
leftColumn->Add(new TextView(mm->T("SavesAreTemporaryGuidance", "Extract PPSSPP somewhere to save permanently"), ALIGN_HCENTER, false));
|
|
leftColumn->Add(new Spacer(10.0f));
|
|
leftColumn->Add(buttonHolder);
|
|
leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f)));
|
|
}
|
|
} else {
|
|
if (!showRecent) {
|
|
leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f));
|
|
// Just so it's destroyed on recreate.
|
|
leftColumn->Add(tabHolder_);
|
|
tabHolder_->SetVisibility(V_GONE);
|
|
}
|
|
|
|
LinearLayout *buttonHolder = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
|
buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f)));
|
|
focusButton = new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
|
focusButton->SetPadding(32, 16);
|
|
buttonHolder->Add(focusButton)->OnClick.Handle(this, &MainScreen::OnAllowStorage);
|
|
buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f)));
|
|
|
|
leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f)));
|
|
leftColumn->Add(buttonHolder);
|
|
leftColumn->Add(new Spacer(10.0f));
|
|
leftColumn->Add(new TextView(mm->T("PPSSPP can't load games or save right now"), ALIGN_HCENTER, false));
|
|
leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f)));
|
|
}
|
|
|
|
ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL);
|
|
LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
rightColumnItems->SetSpacing(0.0f);
|
|
rightColumn->Add(rightColumnItems);
|
|
|
|
char versionString[256];
|
|
sprintf(versionString, "%s", PPSSPP_GIT_VERSION);
|
|
rightColumnItems->SetSpacing(0.0f);
|
|
LinearLayout *logos = new LinearLayout(ORIENT_HORIZONTAL);
|
|
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
|
|
logos->Add(new ImageView(I_ICONGOLD, IS_DEFAULT, new AnchorLayoutParams(64, 64, 10, 10, NONE, NONE, false)));
|
|
} else {
|
|
logos->Add(new ImageView(I_ICON, IS_DEFAULT, new AnchorLayoutParams(64, 64, 10, 10, NONE, NONE, false)));
|
|
}
|
|
logos->Add(new ImageView(I_LOGO, IS_DEFAULT, new LinearLayoutParams(Margins(-12, 0, 0, 0))));
|
|
rightColumnItems->Add(logos);
|
|
TextView *ver = rightColumnItems->Add(new TextView(versionString, new LinearLayoutParams(Margins(70, -6, 0, 0))));
|
|
ver->SetSmall(true);
|
|
ver->SetClip(false);
|
|
#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP)
|
|
rightColumnItems->Add(new Choice(mm->T("Load","Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile);
|
|
#endif
|
|
rightColumnItems->Add(new Choice(mm->T("Game Settings", "Settings")))->OnClick.Handle(this, &MainScreen::OnGameSettings);
|
|
rightColumnItems->Add(new Choice(mm->T("Credits")))->OnClick.Handle(this, &MainScreen::OnCredits);
|
|
rightColumnItems->Add(new Choice(mm->T("www.ppsspp.org")))->OnClick.Handle(this, &MainScreen::OnPPSSPPOrg);
|
|
if (!System_GetPropertyBool(SYSPROP_APP_GOLD)) {
|
|
Choice *gold = rightColumnItems->Add(new Choice(mm->T("Buy PPSSPP Gold")));
|
|
gold->OnClick.Handle(this, &MainScreen::OnSupport);
|
|
gold->SetIcon(I_ICONGOLD);
|
|
}
|
|
|
|
#if !PPSSPP_PLATFORM(UWP)
|
|
// Having an exit button is against UWP guidelines.
|
|
rightColumnItems->Add(new Spacer(25.0));
|
|
rightColumnItems->Add(new Choice(mm->T("Exit")))->OnClick.Handle(this, &MainScreen::OnExit);
|
|
#endif
|
|
|
|
if (vertical) {
|
|
root_ = new LinearLayout(ORIENT_VERTICAL);
|
|
rightColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.75));
|
|
root_->Add(rightColumn);
|
|
root_->Add(leftColumn);
|
|
} else {
|
|
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
|
rightColumn->ReplaceLayoutParams(new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins));
|
|
root_->Add(leftColumn);
|
|
root_->Add(rightColumn);
|
|
}
|
|
|
|
if (focusButton) {
|
|
root_->SetDefaultFocusView(focusButton);
|
|
} else if (tabHolder_->GetVisibility() != V_GONE) {
|
|
root_->SetDefaultFocusView(tabHolder_);
|
|
}
|
|
|
|
auto u = GetI18NCategory("Upgrade");
|
|
|
|
upgradeBar_ = 0;
|
|
if (!g_Config.upgradeMessage.empty()) {
|
|
upgradeBar_ = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
|
|
UI::Margins textMargins(10, 5);
|
|
UI::Margins buttonMargins(0, 0);
|
|
UI::Drawable solid(0xFFbd9939);
|
|
upgradeBar_->SetBG(solid);
|
|
upgradeBar_->Add(new TextView(u->T("New version of PPSSPP available") + std::string(": ") + g_Config.upgradeVersion, new LinearLayoutParams(1.0f, textMargins)));
|
|
upgradeBar_->Add(new Button(u->T("Download"), new LinearLayoutParams(buttonMargins)))->OnClick.Handle(this, &MainScreen::OnDownloadUpgrade);
|
|
upgradeBar_->Add(new Button(u->T("Dismiss"), new LinearLayoutParams(buttonMargins)))->OnClick.Handle(this, &MainScreen::OnDismissUpgrade);
|
|
|
|
// Slip in under root_
|
|
LinearLayout *newRoot = new LinearLayout(ORIENT_VERTICAL);
|
|
newRoot->Add(root_);
|
|
newRoot->Add(upgradeBar_);
|
|
root_->ReplaceLayoutParams(new LinearLayoutParams(1.0));
|
|
root_ = newRoot;
|
|
}
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnAllowStorage(UI::EventParams &e) {
|
|
System_AskForPermission(SYSTEM_PERMISSION_STORAGE);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnDownloadUpgrade(UI::EventParams &e) {
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
// Go to app store
|
|
if (System_GetPropertyBool(SYSPROP_APP_GOLD)) {
|
|
LaunchBrowser("market://details?id=org.ppsspp.ppssppgold");
|
|
} else {
|
|
LaunchBrowser("market://details?id=org.ppsspp.ppsspp");
|
|
}
|
|
#else
|
|
// Go directly to ppsspp.org and let the user sort it out
|
|
LaunchBrowser("https://www.ppsspp.org/downloads.html");
|
|
#endif
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnDismissUpgrade(UI::EventParams &e) {
|
|
g_Config.DismissUpgrade();
|
|
upgradeBar_->SetVisibility(UI::V_GONE);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void MainScreen::sendMessage(const char *message, const char *value) {
|
|
// Always call the base class method first to handle the most common messages.
|
|
UIScreenWithBackground::sendMessage(message, value);
|
|
|
|
if (screenManager()->topScreen() == this) {
|
|
if (!strcmp(message, "boot")) {
|
|
screenManager()->switchScreen(new EmuScreen(value));
|
|
}
|
|
if (!strcmp(message, "browse_folderSelect")) {
|
|
int tab = tabHolder_->GetCurrentTab();
|
|
if (tab >= 0 && tab < (int)gameBrowsers_.size()) {
|
|
gameBrowsers_[tab]->SetPath(value);
|
|
}
|
|
}
|
|
}
|
|
if (!strcmp(message, "permission_granted") && !strcmp(value, "storage")) {
|
|
RecreateViews();
|
|
}
|
|
}
|
|
|
|
void MainScreen::update() {
|
|
UIScreen::update();
|
|
UpdateUIState(UISTATE_MENU);
|
|
bool vertical = UseVerticalLayout();
|
|
if (vertical != lastVertical_) {
|
|
RecreateViews();
|
|
lastVertical_ = vertical;
|
|
}
|
|
}
|
|
|
|
bool MainScreen::UseVerticalLayout() const {
|
|
return dp_yres > dp_xres * 1.1f;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnLoadFile(UI::EventParams &e) {
|
|
if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) {
|
|
System_SendMessage("browse_file", "");
|
|
}
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void MainScreen::DrawBackground(UIContext &dc) {
|
|
UIScreenWithBackground::DrawBackground(dc);
|
|
if (highlightedGamePath_.empty() && prevHighlightedGamePath_.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (DrawBackgroundFor(dc, prevHighlightedGamePath_, 1.0f - prevHighlightProgress_)) {
|
|
if (prevHighlightProgress_ < 1.0f) {
|
|
prevHighlightProgress_ += 1.0f / 20.0f;
|
|
}
|
|
}
|
|
if (!highlightedGamePath_.empty()) {
|
|
if (DrawBackgroundFor(dc, highlightedGamePath_, highlightProgress_)) {
|
|
if (highlightProgress_ < 1.0f) {
|
|
highlightProgress_ += 1.0f / 20.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MainScreen::DrawBackgroundFor(UIContext &dc, const std::string &gamePath, float progress) {
|
|
dc.Flush();
|
|
|
|
std::shared_ptr<GameInfo> ginfo;
|
|
if (!gamePath.empty()) {
|
|
ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath, GAMEINFO_WANTBG);
|
|
// Loading texture data may bind a texture.
|
|
dc.RebindTexture();
|
|
|
|
// Let's not bother if there's no picture.
|
|
if (!ginfo || (!ginfo->pic1.texture && !ginfo->pic0.texture)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
Draw::Texture *texture = nullptr;
|
|
if (ginfo->pic1.texture) {
|
|
texture = ginfo->pic1.texture->GetTexture();
|
|
} else if (ginfo->pic0.texture) {
|
|
texture = ginfo->pic0.texture->GetTexture();
|
|
}
|
|
|
|
uint32_t color = whiteAlpha(ease(progress)) & 0xFFc0c0c0;
|
|
if (texture) {
|
|
dc.GetDrawContext()->BindTexture(0, texture);
|
|
dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color);
|
|
dc.Flush();
|
|
dc.RebindTexture();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnGameSelected(UI::EventParams &e) {
|
|
#ifdef _WIN32
|
|
std::string path = ReplaceAll(e.s, "\\", "/");
|
|
#else
|
|
std::string path = e.s;
|
|
#endif
|
|
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(nullptr, path, GAMEINFO_WANTBG);
|
|
if (ginfo && ginfo->fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) {
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
if (g_GameManager.GetState() == GameManagerState::INSTALLING)
|
|
return UI::EVENT_DONE;
|
|
|
|
// Restore focus if it was highlighted (e.g. by gamepad.)
|
|
restoreFocusGamePath_ = highlightedGamePath_;
|
|
SetBackgroundAudioGame(path);
|
|
lockBackgroundAudio_ = true;
|
|
screenManager()->push(new GameScreen(path));
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnGameHighlight(UI::EventParams &e) {
|
|
using namespace UI;
|
|
|
|
#ifdef _WIN32
|
|
std::string path = ReplaceAll(e.s, "\\", "/");
|
|
#else
|
|
std::string path = e.s;
|
|
#endif
|
|
|
|
// Don't change when re-highlighting what's already highlighted.
|
|
if (path != highlightedGamePath_ || e.a == FF_LOSTFOCUS) {
|
|
if (!highlightedGamePath_.empty()) {
|
|
if (prevHighlightedGamePath_.empty() || prevHighlightProgress_ >= 0.75f) {
|
|
prevHighlightedGamePath_ = highlightedGamePath_;
|
|
prevHighlightProgress_ = 1.0 - highlightProgress_;
|
|
}
|
|
highlightedGamePath_.clear();
|
|
}
|
|
if (e.a == FF_GOTFOCUS) {
|
|
highlightedGamePath_ = path;
|
|
highlightProgress_ = 0.0f;
|
|
}
|
|
}
|
|
|
|
if ((!highlightedGamePath_.empty() || e.a == FF_LOSTFOCUS) && !lockBackgroundAudio_) {
|
|
SetBackgroundAudioGame(highlightedGamePath_);
|
|
}
|
|
|
|
lockBackgroundAudio_ = false;
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnGameSelectedInstant(UI::EventParams &e) {
|
|
#ifdef _WIN32
|
|
std::string path = ReplaceAll(e.s, "\\", "/");
|
|
#else
|
|
std::string path = e.s;
|
|
#endif
|
|
// Go directly into the game.
|
|
screenManager()->switchScreen(new EmuScreen(path));
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnGameSettings(UI::EventParams &e) {
|
|
auto gameSettings = new GameSettingsScreen("", "");
|
|
gameSettings->OnRecentChanged.Handle(this, &MainScreen::OnRecentChange);
|
|
screenManager()->push(gameSettings);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnRecentChange(UI::EventParams &e) {
|
|
RecreateViews();
|
|
if (host) {
|
|
host->UpdateUI();
|
|
}
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnCredits(UI::EventParams &e) {
|
|
screenManager()->push(new CreditsScreen());
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnHomebrewStore(UI::EventParams &e) {
|
|
screenManager()->push(new StoreScreen());
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnSupport(UI::EventParams &e) {
|
|
#ifdef __ANDROID__
|
|
LaunchBrowser("market://details?id=org.ppsspp.ppssppgold");
|
|
#else
|
|
LaunchBrowser("https://central.ppsspp.org/buygold");
|
|
#endif
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnPPSSPPOrg(UI::EventParams &e) {
|
|
LaunchBrowser("https://www.ppsspp.org");
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnForums(UI::EventParams &e) {
|
|
LaunchBrowser("https://forums.ppsspp.org");
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn MainScreen::OnExit(UI::EventParams &e) {
|
|
System_SendMessage("event", "exitprogram");
|
|
|
|
// Request the framework to exit cleanly.
|
|
System_SendMessage("finish", "");
|
|
|
|
// However, let's make sure the config was saved, since it may not have been.
|
|
g_Config.Save("MainScreen::OnExit");
|
|
|
|
#ifdef __ANDROID__
|
|
#ifdef ANDROID_NDK_PROFILER
|
|
moncleanup();
|
|
#endif
|
|
#endif
|
|
|
|
UpdateUIState(UISTATE_EXIT);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
void MainScreen::dialogFinished(const Screen *dialog, DialogResult result) {
|
|
if (dialog->tag() == "store") {
|
|
backFromStore_ = true;
|
|
RecreateViews();
|
|
}
|
|
if (dialog->tag() == "game") {
|
|
if (!restoreFocusGamePath_.empty() && UI::IsFocusMovementEnabled()) {
|
|
// Prevent the background from fading, since we just were displaying it.
|
|
highlightedGamePath_ = restoreFocusGamePath_;
|
|
highlightProgress_ = 1.0f;
|
|
|
|
// Refocus the game button itself.
|
|
int tab = tabHolder_->GetCurrentTab();
|
|
if (tab >= 0 && tab < (int)gameBrowsers_.size()) {
|
|
gameBrowsers_[tab]->FocusGame(restoreFocusGamePath_);
|
|
}
|
|
|
|
// Don't get confused next time.
|
|
restoreFocusGamePath_.clear();
|
|
} else {
|
|
// Not refocusing, so we need to stop the audio.
|
|
SetBackgroundAudioGame("");
|
|
}
|
|
}
|
|
}
|
|
|
|
void UmdReplaceScreen::CreateViews() {
|
|
using namespace UI;
|
|
Margins actionMenuMargins(0, 100, 15, 0);
|
|
auto mm = GetI18NCategory("MainMenu");
|
|
auto di = GetI18NCategory("Dialog");
|
|
|
|
TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0));
|
|
leftColumn->SetTag("UmdReplace");
|
|
leftColumn->SetClip(true);
|
|
|
|
ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(270, FILL_PARENT, actionMenuMargins));
|
|
LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
rightColumnItems->SetSpacing(0.0f);
|
|
rightColumn->Add(rightColumnItems);
|
|
|
|
if (g_Config.iMaxRecent > 0) {
|
|
ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
scrollRecentGames->SetTag("UmdReplaceRecentGames");
|
|
GameBrowser *tabRecentGames = new GameBrowser(
|
|
"!RECENT", BrowseFlags::NONE, &g_Config.bGridView1, "", "",
|
|
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
|
|
scrollRecentGames->Add(tabRecentGames);
|
|
leftColumn->AddTab(mm->T("Recent"), scrollRecentGames);
|
|
tabRecentGames->OnChoice.Handle(this, &UmdReplaceScreen::OnGameSelectedInstant);
|
|
tabRecentGames->OnHoldChoice.Handle(this, &UmdReplaceScreen::OnGameSelected);
|
|
}
|
|
ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
|
|
scrollAllGames->SetTag("UmdReplaceAllGames");
|
|
|
|
GameBrowser *tabAllGames = new GameBrowser(g_Config.currentDirectory, BrowseFlags::STANDARD, &g_Config.bGridView2,
|
|
mm->T("How to get games"), "https://www.ppsspp.org/getgames.html",
|
|
new LinearLayoutParams(FILL_PARENT, FILL_PARENT));
|
|
|
|
scrollAllGames->Add(tabAllGames);
|
|
|
|
leftColumn->AddTab(mm->T("Games"), scrollAllGames);
|
|
|
|
tabAllGames->OnChoice.Handle(this, &UmdReplaceScreen::OnGameSelectedInstant);
|
|
|
|
tabAllGames->OnHoldChoice.Handle(this, &UmdReplaceScreen::OnGameSelected);
|
|
|
|
rightColumnItems->Add(new Choice(di->T("Cancel")))->OnClick.Handle(this, &UmdReplaceScreen::OnCancel);
|
|
rightColumnItems->Add(new Choice(mm->T("Game Settings")))->OnClick.Handle(this, &UmdReplaceScreen::OnGameSettings);
|
|
|
|
if (g_Config.recentIsos.size() > 0) {
|
|
leftColumn->SetCurrentTab(0, true);
|
|
} else if (g_Config.iMaxRecent > 0) {
|
|
leftColumn->SetCurrentTab(1, true);
|
|
}
|
|
|
|
root_ = new LinearLayout(ORIENT_HORIZONTAL);
|
|
root_->Add(leftColumn);
|
|
root_->Add(rightColumn);
|
|
}
|
|
|
|
void UmdReplaceScreen::update() {
|
|
UpdateUIState(UISTATE_PAUSEMENU);
|
|
UIScreen::update();
|
|
}
|
|
|
|
UI::EventReturn UmdReplaceScreen::OnGameSelected(UI::EventParams &e) {
|
|
__UmdReplace(e.s);
|
|
TriggerFinish(DR_OK);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
UI::EventReturn UmdReplaceScreen::OnCancel(UI::EventParams &e) {
|
|
TriggerFinish(DR_CANCEL);
|
|
return UI::EVENT_DONE;
|
|
}
|
|
|
|
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);
|
|
TriggerFinish(DR_OK);
|
|
return UI::EVENT_DONE;
|
|
}
|