Merge pull request #22880 from hrydgard/gamebutton-cleanup

GameButton code cleanup
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-11-01 11:42:50 +01:00
commit fb93fad41c
14 files changed
+788 -740

No files matched your search

+1 -1
View File
@@ -59,7 +59,7 @@ public:
}
bool Valid() const { return !path_.empty(); }
bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash.
bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash - except see below.
// On Windows, "//" is the network folder that lists the servers, the other path that ends in a slash.
bool IsNetworkRoot() const {
#if PPSSPP_PLATFORM(WINDOWS)
+3
View File
@@ -133,6 +133,9 @@ public:
}
// Results in 18 triangles. Kind of expensive for a button.
void DrawImage4Grid(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color = COLOR(0xFFFFFF), float corner_scale = 1.0);
void DrawImage4Grid(ImageID atlas_image, const Bounds &bounds, Color color = COLOR(0xFFFFFF), float corner_scale = 1.0) {
DrawImage4Grid(atlas_image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color, corner_scale);
}
// Uses the nearest precomputed corner radius, scaled to fit, so outlines get thinner or thicker
// when radius isn't one of ROUND_RECT_RADII. 18 triangles filled, 16 outlined.
+2
View File
@@ -53,6 +53,8 @@ list(APPEND UISource
MainScreen.cpp
GameBrowser.h
GameBrowser.cpp
GameButton.h
GameButton.cpp
MiscScreens.h
MiscScreens.cpp
MiscViews.h
+2 -2
View File
@@ -665,7 +665,7 @@ void EmuScreen::sendMessage(UIMessage message, const char *value) {
// temporary workaround for hotkey its freeze the ui when open chat screen using hotkey and native keyboard is enable
if (g_Config.bBypassOSKWithKeyboard) {
// TODO: Make translatable.
g_OSD.Show(OSDType::MESSAGE_INFO, "Disable \"Use system native keyboard\" to use chat hotkey", 2.0f);
g_OSD.Show(OSDType::MESSAGE_INFO, "Disable \"Use system native keyboard\" to use chat hotkey", 2.0f, "chatHotKey");
} else {
OpenChat(true);
}
@@ -943,7 +943,7 @@ void EmuScreen::ProcessVKey(VirtKey virtKey, bool down) {
if (SaveState::CanRewind()) {
SaveState::Rewind(&ShowMessageAfterSaveStateAction);
} else {
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0);
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0, "norewind");
}
}
}
+34 -735
View File
@@ -27,7 +27,6 @@
#include "Common/UI/Root.h"
#include "Common/UI/ScreenManager.h"
#include "Common/Math/curves.h"
#include "Common/Net/URL.h"
#include "Common/File/FileUtil.h"
#include "Common/TimeUtil.h"
@@ -40,10 +39,9 @@
#include "Core/ELF/PBPReader.h"
#include "Core/FileLoaders/PS1EbootFileLoader.h"
#include "Core/MemMap.h"
#include "Core/Util/VideoPlayer.h"
#include "UI/GameBrowser.h"
#include "UI/GamePacker.h"
#include "UI/GameButton.h"
#include "UI/RemoteISOScreen.h"
#include "UI/SavedataScreen.h"
#include "UI/Store.h"
@@ -53,720 +51,9 @@
#include "Common/Data/Text/I18n.h"
#include "Core/Util/DarwinFileSystemServices.h" // For the browser
// Icon dimensions in the large list, and its padding around the icon.
static constexpr float LARGE_LIST_ICON_W = 144.0f;
static constexpr float LARGE_LIST_ICON_H = 80.0f;
static constexpr float LARGE_LIST_PAD = 6.0f;
static constexpr float LARGE_LIST_LEFT_PAD = 11.0f;
static constexpr float LARGE_LIST_INFO_SIZE = 64.0f;
// Folders get bigger lines too, though not as big as the games.
static constexpr float LARGE_LIST_FOLDER_H = 50.0f;
// Where the text starts in the list modes.
static float ListTextX(GameBrowserViewMode viewMode) {
if (viewMode == GameBrowserViewMode::LARGE_LIST) {
return LARGE_LIST_LEFT_PAD + LARGE_LIST_ICON_W * g_Config.fGameGridScale + 20.0f;
}
return 150.0f;
}
// Space to the right of the text in the list modes, for the info button.
static float ListTextRightMargin(GameBrowserViewMode viewMode) {
if (viewMode == GameBrowserViewMode::LARGE_LIST) {
return LARGE_LIST_INFO_SIZE + LARGE_LIST_PAD * 2;
}
return 0.0f;
}
// In the list views, rightText (like a game ID) goes at the right edge, as for a game with a picture.
static void DrawIconWithText(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, GameBrowserViewMode viewMode, const UI::Style &style, std::string_view rightText = {}) {
const bool gridStyle = viewMode == GameBrowserViewMode::GRID;
float tw, th;
dc.MeasureText(dc.GetFontStyle(), gridStyle ? g_Config.fGameGridScale : 1.0, gridStyle ? g_Config.fGameGridScale : 1.0, text, &tw, &th, 0);
const bool compact = bounds.w < 180 * (gridStyle ? g_Config.fGameGridScale : 1.0);
if (compact) {
dc.PushScissor(bounds);
const FontStyle *fontStyle = GetTextStyle(dc, UI::TextSize::Small);
dc.SetFontStyle(*GetTextStyle(dc, UI::TextSize::Small));
int iconSize = image == ImageID("I_UP_DIRECTORY") ? (float)dc.Draw()->GetAtlas()->getImage(image)->h : bounds.h * 0.3f;
float textWidth = 0.0f;
float textHeight = 0;
dc.MeasureTextRect(*fontStyle, 1.0f, 1.0f, text, bounds.w - 10, &textWidth, &textHeight, ALIGN_HCENTER | FLAG_WRAP_TEXT);
int totalHeight = iconSize + (int)textHeight;
const float y = std::max(0.0f, bounds.h / 2.0f - totalHeight / 2.0f);
if (image.isValid()) {
const AtlasImage *img = dc.Draw()->GetAtlas()->getImage(image);
if (img && img->h > 0) {
dc.RebindTexture();
dc.Draw()->DrawImage(image, bounds.centerX(), bounds.y + y + 2, iconSize / (float)img->h, style.fgColor, ALIGN_TOP | ALIGN_HCENTER);
}
}
if (image != ImageID("I_UP_DIRECTORY") && image != ImageID("I_PIN") && image != ImageID("I_UNPIN")) {
dc.DrawTextRect(text, bounds.Inset(5, y + iconSize + 4, 5, 2), style.fgColor, ALIGN_HCENTER | FLAG_WRAP_TEXT);
}
dc.SetFontStyle(dc.GetTheme().uiFont);
dc.PopScissor();
} else {
const float textX = ListTextX(viewMode);
if (viewMode == GameBrowserViewMode::LARGE_LIST) {
const float scale = g_Config.fGameGridScale;
dc.Draw()->DrawImage(image, bounds.x + LARGE_LIST_LEFT_PAD + LARGE_LIST_ICON_W * 0.5f * scale, bounds.centerY(), 0.88f * scale, style.fgColor, ALIGN_CENTER);
} else {
dc.Draw()->DrawImage(image, bounds.x + 72, bounds.centerY(), 0.88f * (gridStyle ? g_Config.fGameGridScale : 1.0), style.fgColor, ALIGN_CENTER);
}
float tx = textX;
int availableWidth = bounds.w - textX - ListTextRightMargin(viewMode);
if (!rightText.empty()) {
float vw, vh;
dc.MeasureText(dc.GetFontStyle(), 0.7f, 0.7f, rightText, &vw, &vh, 0);
availableWidth -= vw + 20;
dc.SetFontScale(0.7f, 0.7f);
dc.DrawText(rightText, bounds.x + availableWidth + textX + 10, bounds.centerY(), style.fgColor, ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
}
float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
if (availableWidth < tw) {
tx -= (1.0f + sin(time_now_d() * 1.5f)) * sineWidth;
Bounds tb = bounds;
tb.x = bounds.x + textX;
tb.w = availableWidth;
dc.PushScissor(tb);
}
dc.DrawText(text, bounds.x + tx, bounds.centerY(), style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT);
if (availableWidth < tw) {
dc.PopScissor();
}
}
}
class GameButton : public UI::Clickable {
public:
GameButton(const Path &gamePath, GameBrowserViewMode viewMode, UI::LayoutParams *layoutParams = nullptr)
: UI::Clickable(layoutParams), viewMode_(viewMode), gamePath_(gamePath) {}
void Draw(UIContext &dc) override;
std::string DescribeText() const override;
std::string SearchAlias() const override {
// Without the extension, or searching for "iso" would match every ISO. Only three or more
// letters counts as one, so a folder like "Game v1.2" keeps its version number.
std::string filename = gamePath_.GetFilename();
const std::string ext = gamePath_.GetFileExtension();
if (ext.size() >= 4 && ext.size() <= filename.size() && std::all_of(ext.begin() + 1, ext.end(), [](char c) { return c >= 'a' && c <= 'z'; })) {
filename.resize(filename.size() - ext.size());
}
return filename;
}
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
switch (viewMode_) {
case GameBrowserViewMode::GRID:
w = 144 * g_Config.fGameGridScale;
h = 80 * g_Config.fGameGridScale;
break;
case GameBrowserViewMode::LARGE_LIST:
w = 500;
h = LARGE_LIST_ICON_H * g_Config.fGameGridScale + LARGE_LIST_PAD * 2;
break;
default: // List
w = 500;
h = 50;
break;
}
}
const Path &GamePath() const { return gamePath_; }
void SetHoldEnabled(bool hold) {
holdEnabled_ = hold;
}
bool Touch(const TouchInput &input) override {
hovering_ = bounds_.Contains(input.x, input.y);
if (HasInfoButton() && TouchInfoButton(input)) {
return true;
}
const bool retval = UI::Clickable::Touch(input);
if (hovering_ && (input.flags & TouchInputFlags::DOWN)) {
holdStart_ = time_now_d();
}
if (input.flags & TouchInputFlags::UP) {
holdStart_ = 0;
}
return retval;
}
bool Key(const KeyInput &key) override {
bool showInfo = false;
if (HasFocus() && UI::IsInfoKey(key)) {
// If it's the button that's mapped to triangle, then show the info.
if (key.flags & KeyInputFlags::DOWN) {
infoKeyPressed_ = true;
}
if ((key.flags & KeyInputFlags::UP) && infoKeyPressed_) {
showInfo = true;
infoKeyPressed_ = false;
}
} 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 & KeyInputFlags::DOWN) {
showInfoPressed_ = true;
}
if ((key.flags & KeyInputFlags::UP) && showInfoPressed_) {
showInfo = true;
showInfoPressed_ = false;
}
}
if (showInfo) {
TriggerOnHoldClick();
return true;
}
if (infoSelected_ && HasFocus() && UI::IsAcceptKey(key)) {
if (key.flags & KeyInputFlags::DOWN) {
infoButtonDown_ = true;
}
if ((key.flags & KeyInputFlags::UP) && infoButtonDown_) {
infoButtonDown_ = false;
TriggerOnHoldClick();
}
return true;
}
// Holding the accept button works like a long press, so a gamepad can do it too.
if (holdEnabled_ && HasFocus() && UI::IsAcceptKey(key) && !(key.flags & KeyInputFlags::IS_REPEAT)) {
if (key.flags & KeyInputFlags::DOWN) {
holdStart_ = time_now_d();
}
if (key.flags & KeyInputFlags::UP) {
holdStart_ = 0.0;
}
}
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(UI::FocusFlags focusFlags) override {
UI::Clickable::FocusChanged(focusFlags);
if (focusFlags & UI::FocusFlags::LOST_FOCUS) {
// Don't let a held accept button carry over from a button we've left.
holdStart_ = 0.0;
// Moving to another game selects the game, not its info button.
infoSelected_ = false;
infoButtonDown_ = false;
}
TriggerOnHighlight(focusFlags);
}
// Left and right move between the game and its info button, so up and down still go from game to game.
bool CanMoveFocus(UI::FocusMove dir) const override {
if (!HasInfoButton()) {
return true;
}
// We handle these moves ourselves, so MoveFocus won't play the sound for them.
if (dir == UI::FocusMove::RIGHT && !infoSelected_) {
infoSelected_ = true;
UI::PlayUISound(UI::UISound::SELECT);
return false;
}
if (dir == UI::FocusMove::LEFT && infoSelected_) {
infoSelected_ = false;
UI::PlayUISound(UI::UISound::SELECT);
return false;
}
return true;
}
void DeviceLost() override {
if (video_) {
video_->DeviceLost();
}
}
UI::Event OnHoldClick;
UI::Event OnHighlight;
private:
void TriggerOnHoldClick() {
holdStart_ = 0.0;
UI::EventParams e{};
e.v = this;
e.s = gamePath_.ToString();
down_ = false;
OnHoldClick.Trigger(e);
}
void TriggerOnHighlight(UI::FocusFlags focusFlags) {
UI::EventParams e{};
e.v = this;
e.s = gamePath_.ToString();
e.a = (u32)focusFlags;
OnHighlight.Trigger(e);
}
void UpdateVideo(const std::shared_ptr<GameInfo> &ginfo, bool wantVideo);
// The large list has room for an info button on each line, doing the same as a long press.
bool HasInfoButton() const {
return viewMode_ == GameBrowserViewMode::LARGE_LIST && holdEnabled_;
}
Bounds InfoButtonBounds() const {
return Bounds(bounds_.x2() - LARGE_LIST_INFO_SIZE - LARGE_LIST_PAD, bounds_.centerY() - LARGE_LIST_INFO_SIZE * 0.5f, LARGE_LIST_INFO_SIZE, LARGE_LIST_INFO_SIZE);
}
// Returns true if the info button took the input.
bool TouchInfoButton(const TouchInput &input) {
if ((input.flags & TouchInputFlags::MOUSE) && (input.buttons & 1) == 0) {
return false;
}
const bool inside = InfoButtonBounds().Contains(input.x, input.y);
if (input.flags & TouchInputFlags::DOWN) {
infoButtonDown_ = inside;
}
if (!infoButtonDown_) {
return false;
}
if (input.flags & TouchInputFlags::UP) {
infoButtonDown_ = false;
if (inside && !(input.flags & TouchInputFlags::CANCEL)) {
TriggerOnHoldClick();
}
}
return true;
}
const GameBrowserViewMode viewMode_;
const Path gamePath_;
std::string title_;
// The ICON1.PMF video, played in place of the icon in the large list, like the XMB does.
std::unique_ptr<PMFVideoTexture> video_;
Instant videoStart_;
double holdStart_ = 0.0;
bool holdEnabled_ = true;
bool showInfoPressed_ = false;
bool infoKeyPressed_ = false;
bool hovering_ = false;
bool infoButtonDown_ = false;
// The info button has the keyboard focus, rather than the game. Changed from CanMoveFocus, hence mutable.
mutable bool infoSelected_ = false;
};
void GameButton::UpdateVideo(const std::shared_ptr<GameInfo> &ginfo, bool wantVideo) {
if (!wantVideo) {
video_.reset();
return;
}
if (video_ || !ginfo->Ready(GameInfoFlags::ICON1_PMF)) {
return;
}
std::string data;
{
std::lock_guard<std::mutex> guard(ginfo->lock);
data = ginfo->icon1pmf;
}
if (!data.empty()) {
video_ = std::make_unique<PMFVideoTexture>(std::move(data));
videoStart_ = Instant::Now();
}
}
void GameButton::Draw(UIContext &dc) {
// Like the XMB, only the selected game plays its video. Only keyboard/gamepad focus selects, since with
// touch or the mouse, getting a video to play without launching the game was too fiddly.
const bool wantVideo = viewMode_ == GameBrowserViewMode::LARGE_LIST && HasFocus() && pmf_player_available();
GameInfoFlags infoFlags = GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON;
if (wantVideo) {
infoFlags |= GameInfoFlags::ICON1_PMF;
}
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, infoFlags);
UpdateVideo(ginfo, wantVideo);
Draw::Texture *texture = nullptr;
u32 color = 0, shadowColor = 0;
using namespace UI;
UI::Style style = dc.GetTheme().itemStyle;
if (down_) {
style = dc.GetTheme().itemDownStyle;
}
// Some types we just draw a default icon for.
ImageID imageIcon = ImageID::invalid();
bool drawBackground = true;
switch (ginfo->fileType) {
case IdentifiedFileType::PSP_ELF:
imageIcon = ImageID("I_APP");
// The XMB is an ELF too, but has an icon.
drawBackground = ginfo->region == GameRegion::VSH;
break;
case IdentifiedFileType::UNKNOWN_ELF: imageIcon = ImageID("I_APP"); drawBackground = false; break;
case IdentifiedFileType::PPSSPP_GE_DUMP: imageIcon = ImageID("I_DISPLAY"); break;
case IdentifiedFileType::PSX_ISO:
case IdentifiedFileType::PSP_PS1_PBP: imageIcon = ImageID("I_PSX_ISO"); break;
case IdentifiedFileType::PS2_ISO: imageIcon = ImageID("I_PS2_ISO"); break;
case IdentifiedFileType::PS3_ISO: imageIcon = ImageID("I_PS3_ISO"); break;
case IdentifiedFileType::PSP_UMD_VIDEO_ISO: imageIcon = ImageID("I_UMD_VIDEO_ISO"); break;
case IdentifiedFileType::UNKNOWN_ISO: imageIcon = ImageID("I_UNKNOWN_ISO"); break;
case IdentifiedFileType::PPSSPP_SAVESTATE:
case IdentifiedFileType::ERROR_IDENTIFYING:
case IdentifiedFileType::UNKNOWN_BIN: imageIcon = ImageID("I_FILE"); break;
case IdentifiedFileType::ARCHIVE_ZIP: imageIcon = ImageID("I_ARCHIVE_ZIP"); drawBackground = false; break;
case IdentifiedFileType::ARCHIVE_7Z: imageIcon = ImageID("I_ARCHIVE_7Z"); drawBackground = false; break;
case IdentifiedFileType::ARCHIVE_RAR: imageIcon = ImageID("I_ARCHIVE_RAR"); drawBackground = false; break;
case IdentifiedFileType::PSP_PKG: imageIcon = ImageID("I_FOLDER_UPLOAD"); drawBackground = false; break;
default: break;
}
if (ginfo->Ready(GameInfoFlags::ICON)) {
if (ginfo->icon.texture && drawBackground) {
texture = ginfo->icon.texture;
// The type icon is only for when there's no picture, and would be drawn on top of it.
imageIcon = ImageID::invalid();
} else if (drawBackground) {
// No icon, but drawBackground is set. Let's show a plain icon depending on type.
if (ginfo->fileType == IdentifiedFileType::PSP_ISO || ginfo->fileType == IdentifiedFileType::PSP_ISO_NP) {
imageIcon = ImageID("I_UMD");
} else if (ginfo->fileType == IdentifiedFileType::PSX_ISO || ginfo->fileType == IdentifiedFileType::PSP_PS1_PBP) {
imageIcon = ImageID("I_PSX_ISO");
} else if (gamePath_.Type() == PathType::HTTP) {
imageIcon = ginfo->fileType == IdentifiedFileType::UNKNOWN ? ImageID("I_LINK_OUT_QUESTION") : ImageID("I_LINK_OUT");
} else {
imageIcon = ImageID("I_APP");
}
}
} else {
// Make sure that pending HTTP icons show something, instead of just blank.
if (gamePath_.Type() == PathType::HTTP) {
imageIcon = ImageID("I_LINK_OUT_QUESTION");
}
}
Bounds overlayBounds = bounds_;
u32 overlayColor = 0;
if (holdEnabled_ && holdStart_ != 0.0) {
double time_held = time_now_d() - holdStart_;
overlayColor = whiteAlpha(time_held / 2.5f);
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)
overlayColor = 0x0;
}
}
}
int x = bounds_.x;
int y = bounds_.y;
int w = viewMode_ == GameBrowserViewMode::GRID ? bounds_.w : 144;
int h = bounds_.h;
if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
x += LARGE_LIST_LEFT_PAD;
y += LARGE_LIST_PAD;
w = LARGE_LIST_ICON_W * g_Config.fGameGridScale;
h = LARGE_LIST_ICON_H * g_Config.fGameGridScale;
}
const float textX = ListTextX(viewMode_);
const bool rowFocused = HasFocus() && !infoSelected_;
if (viewMode_ != GameBrowserViewMode::GRID || !texture) {
if (rowFocused)
style = down_ ? dc.GetTheme().itemDownStyle : dc.GetTheme().itemFocusedStyle;
Drawable bg = style.background;
dc.Draw()->Flush();
dc.RebindTexture();
dc.FillRect(bg, bounds_);
dc.Draw()->Flush();
}
if (texture && video_) {
Draw::Texture *frame = video_->Update(dc.GetDrawContext(), videoStart_.ElapsedSeconds());
if (frame) {
texture = frame;
}
}
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, (float)w);
x += (w - nw) / 2.0f;
w = nw;
}
int txOffset = down_ ? 4 : 0;
if (viewMode_ != GameBrowserViewMode::GRID) txOffset = 0;
// Render button
int dropsize = 10;
if (texture) {
if (viewMode_ == GameBrowserViewMode::LIST) {
x += 4;
}
if (txOffset) {
dropsize = 3;
y += txOffset * 2;
overlayBounds.y += txOffset * 2;
}
if (HasFocus()) {
dc.Draw()->Flush();
dc.RebindTexture();
float pulse = sin(time_now_d() * 7.0) * 0.25 + 0.8;
dc.Draw()->DrawImage4Grid(dc.GetTheme().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.GetTheme().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);
dc.Draw()->DrawTexRect(x, y, x + w, y + h, 0, 0, 1, 1, color);
dc.Draw()->Flush();
}
if (viewMode_ == GameBrowserViewMode::GRID && g_Config.bShowIDOnGameIcon && ginfo->fileType != IdentifiedFileType::PSP_ELF && !ginfo->id_version.empty()) {
std::string_view idStr = ginfo->id_version;
if (ginfo->fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) {
auto ga = GetI18NCategory(I18NCat::GAME);
idStr = ga->T("SaveData");
}
dc.SetFontScale(0.5f * g_Config.fGameGridScale, 0.5f * g_Config.fGameGridScale);
dc.DrawText(idStr, bounds_.x + 5, y + 1, 0xFF000000, ALIGN_TOPLEFT);
dc.DrawText(idStr, bounds_.x + 4, y, dc.GetTheme().infoStyle.fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(1.0f, 1.0f);
}
if (imageIcon.isValid()) {
Style style = dc.GetTheme().itemStyle;
if (rowFocused) style = dc.GetTheme().itemFocusedStyle;
if (down_) style = dc.GetTheme().itemDownStyle;
if (!IsEnabled()) style = dc.GetTheme().itemDisabledStyle;
DrawIconWithText(dc, imageIcon, ginfo->GetTitle(), bounds_, viewMode_, style, g_Config.bShowIDOnGameIcon ? std::string_view(ginfo->id_version) : std::string_view());
if (overlayColor) {
dc.FillRect(Drawable(overlayColor), overlayBounds);
}
} else {
char discNumInfo[8];
if (ginfo->disc_total > 1)
snprintf(discNumInfo, sizeof(discNumInfo), "-DISC%d", ginfo->disc_number);
else
discNumInfo[0] = '\0';
dc.Draw()->Flush();
dc.RebindTexture();
dc.SetFontStyle(dc.GetTheme().uiFont);
if (viewMode_ != GameBrowserViewMode::GRID) {
float tw, th;
dc.Draw()->Flush();
dc.PushScissor(bounds_);
const std::string currentTitle = ginfo->GetTitle();
if (!currentTitle.empty()) {
title_ = ReplaceAll(currentTitle, "\n", " ");
}
dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, title_, &tw, &th, 0);
int availableWidth = bounds_.w - textX - ListTextRightMargin(viewMode_);
if (g_Config.bShowIDOnGameIcon) {
float vw, vh;
dc.MeasureText(dc.GetFontStyle(), 0.7f, 0.7f, ginfo->id_version, &vw, &vh, 0);
availableWidth -= vw + 20;
dc.SetFontScale(0.7f, 0.7f);
dc.DrawText(ginfo->id_version, bounds_.x + availableWidth + textX + 10, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
}
float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
float tx = textX;
if (availableWidth < tw) {
tx -= (1.0f + sin(time_now_d() * 1.5f)) * sineWidth;
Bounds tb = bounds_;
tb.x = bounds_.x + textX;
tb.w = availableWidth;
dc.PushScissor(tb);
}
dc.DrawText(title_, 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_, bounds_.x + 4, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
dc.Draw()->Flush();
dc.PopScissor();
} else {
dc.Draw()->Flush();
}
}
if (ginfo->hasConfig && !ginfo->id.empty()) {
const AtlasImage *gearImage = dc.Draw()->GetAtlas()->getImage(ImageID("I_GEAR_SMALL"));
if (gearImage) {
if (viewMode_ == GameBrowserViewMode::GRID) {
dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x, y + h - gearImage->h * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x + LARGE_LIST_LEFT_PAD, y + h - gearImage->h * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else {
dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x + 4, y, 1.0f);
}
}
}
const int regionIndex = (int)ginfo->region;
if (g_Config.bShowRegionOnGameIcon && regionIndex >= 0 && regionIndex < (int)GameRegion::COUNT) {
const ImageID regionIcons[(int)GameRegion::COUNT] = {
ImageID("I_FLAG_JP"),
ImageID("I_FLAG_US"),
ImageID("I_FLAG_EU"),
ImageID("I_FLAG_HK"),
ImageID("I_FLAG_AS"),
ImageID("I_FLAG_KO"),
};
const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(regionIcons[regionIndex]);
if (image) {
if (viewMode_ == GameBrowserViewMode::GRID) {
dc.Draw()->DrawImage(regionIcons[regionIndex], bounds_.x + bounds_.w - (image->w + 5) * g_Config.fGameGridScale,
y + h - (image->h + 5) * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
dc.Draw()->DrawImage(regionIcons[regionIndex], bounds_.x + LARGE_LIST_LEFT_PAD + (LARGE_LIST_ICON_W - image->w - 5) * g_Config.fGameGridScale,
y + h - (image->h + 5) * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else {
dc.Draw()->DrawImage(regionIcons[regionIndex], bounds_.x + 4, y + h - image->h - 5, 1.0f);
}
}
}
if (HasInfoButton()) {
const Bounds infoBounds = InfoButtonBounds();
Style infoStyle = HasFocus() ? dc.GetTheme().itemFocusedStyle : dc.GetTheme().itemStyle;
if (infoButtonDown_) {
infoStyle = dc.GetTheme().itemDownStyle;
dc.FillRoundRect(infoStyle.background, infoBounds, dc.GetTheme().choiceRoundRadius);
} else if (HasFocus() && infoSelected_) {
dc.FillRoundRect(infoStyle.background, infoBounds, dc.GetTheme().choiceRoundRadius);
}
dc.Draw()->DrawImage(ImageID("I_INFO"), infoBounds.centerX(), infoBounds.centerY(), 1.0f, infoStyle.fgColor, ALIGN_CENTER);
}
if (overlayColor) {
dc.FillRect(Drawable(overlayColor), overlayBounds);
}
// Covers the icon while the game is being compressed. Clicking opens the game screen instead of playing.
GamePacker::Status packStatus;
if (GamePacker::IsPacking(gamePath_) && GamePacker::GetStatus(&packStatus)) {
auto ga = GetI18NCategory(I18NCat::GAME);
const Bounds iconBounds(x, y, w, h);
dc.FillRect(Drawable(0xD0505050), iconBounds);
const float scale = viewMode_ == GameBrowserViewMode::GRID || viewMode_ == GameBrowserViewMode::LARGE_LIST ? 0.8f * g_Config.fGameGridScale : 0.6f;
dc.SetFontStyle(dc.GetTheme().uiFont);
dc.SetFontScale(scale, scale);
const std::string text = std::string(ga->T("Compressing...")) + StringFromFormat("\n%d%%", (int)(packStatus.progress * 100.0f));
dc.DrawTextRectSqueeze(text, iconBounds, 0xFFFFFFFF, ALIGN_CENTER);
dc.SetFontScale(1.0f, 1.0f);
}
dc.RebindTexture();
}
std::string GameButton::DescribeText() const {
// LOW: search calls this for every game in the list, which mustn't hold up the visible ones.
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO, nullptr, GameInfoFlags::EMPTY, TaskPriority::LOW);
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO))
return "...";
auto u = GetI18NCategory(I18NCat::UI_ELEMENTS);
return ginfo->GetTitle();
}
class DirButton : public UI::Button {
public:
DirButton(const Path &path, GameBrowserViewMode viewMode, UI::LayoutParams *layoutParams)
: UI::Button(path.ToString(), layoutParams), path_(path), viewMode_(viewMode), absolute_(false) {}
DirButton(const Path &path, const std::string &text, GameBrowserViewMode viewMode, UI::LayoutParams *layoutParams = 0)
: UI::Button(text, layoutParams), path_(path), viewMode_(viewMode), absolute_(true) {}
void Draw(UIContext &dc) override;
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override {
UI::Button::GetContentDimensions(dc, w, h);
if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
h = std::max(h, LARGE_LIST_FOLDER_H * g_Config.fGameGridScale + LARGE_LIST_PAD * 2);
}
}
const Path &GetPath() const {
return path_;
}
bool PathAbsolute() const {
return absolute_;
}
void SetPinned(bool pin) {
pinned_ = pin;
}
std::string DescribeText() const override {
return std::string(Button::GetText()); // From Button
}
private:
Path path_;
GameBrowserViewMode viewMode_;
bool absolute_;
bool pinned_ = false;
};
void DirButton::Draw(UIContext &dc) {
using namespace UI;
std::string_view text(GetText());
ImageID image = ImageID(pinned_ ? "I_FOLDER_PINNED" : "I_FOLDER");
if (path_.IsNetworkRoot()) {
image = ImageID("I_NETWORK");
} else if (text == "..") {
image = ImageID("I_UP_DIRECTORY");
text = "";
}
Style style = dc.GetTheme().itemStyle;
if (HasFocus()) style = dc.GetTheme().itemFocusedStyle;
if (down_) style = dc.GetTheme().itemDownStyle;
if (!IsEnabled()) style = dc.GetTheme().itemDisabledStyle;
dc.FillRect(style.background, bounds_);
DrawIconWithText(dc, image, text, bounds_, viewMode_, style);
}
GameBrowser::GameBrowser(int token, const Path &path, BrowseFlags browseFlags, bool portrait, int *viewMode, ScreenManager *screenManager, std::string_view lastText, std::string_view lastLink, UI::LayoutParams *layoutParams)
: LinearLayout(ORIENT_VERTICAL, layoutParams), viewMode_(viewMode), browseFlags_(browseFlags), portrait_(portrait), lastText_(lastText), lastLink_(lastLink), screenManager_(screenManager), token_(token) {
using namespace UI;
@@ -787,19 +74,19 @@ GameBrowser::GameBrowser(int token, const Path &path, BrowseFlags browseFlags, b
Refresh();
}
void GameBrowser::FocusGame(const Path &gamePath) {
void GameBrowser::FocusGame(const Path &GetPath) {
// If the button's still there, just focus it. That keeps the search results and scroll position.
if (gameList_) {
for (int i = 0; i < gameList_->GetNumSubviews(); i++) {
GameButton *button = dynamic_cast<GameButton *>(gameList_->GetViewByIndex(i));
if (button && button->GamePath() == gamePath && button->GetVisibility() == UI::V_VISIBLE) {
if (button && button->GetPath() == GetPath && button->GetVisibility() == UI::V_VISIBLE) {
button->SetFocus(UI::FocusFlags::CAUSE_RESTORE);
return;
}
}
}
// Otherwise, Refresh focuses it when it recreates the buttons.
focusPath_ = gamePath;
focusPath_ = GetPath;
refreshPending_ = true;
}
@@ -1139,7 +426,7 @@ void GameBrowser::Refresh() {
Add(gameList_);
// Find games in the current directory and create new ones.
std::vector<DirButton *> dirButtons;
std::vector<GameButton *> dirButtons;
std::vector<GameButton *> gameButtons;
listingPending_ = !path_.IsListingReady();
@@ -1149,7 +436,7 @@ void GameBrowser::Refresh() {
std::vector<Path> filenames;
if (HasSpecialFiles(filenames)) {
for (size_t i = 0; i < filenames.size(); i++) {
gameButtons.push_back(new GameButton(filenames[i], lastViewMode_, new UI::LinearLayoutParams(gridStyle ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT)));
gameButtons.push_back(new GameButton(GameButtonType::Game, filenames[i], lastViewMode_, new UI::LinearLayoutParams(gridStyle ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT)));
}
} else if (!listingPending_) {
std::vector<File::FileInfo> fileInfo;
@@ -1173,11 +460,13 @@ void GameBrowser::Refresh() {
if (!isGame && !isSaveData) {
if (browseFlags_ & BrowseFlags::NAVIGATE) {
const std::string name = fileInfo[i].fullName.IsNetworkRoot() ? std::string(mm->T("Network")) : fileInfo[i].name;
dirButtons.push_back(new DirButton(fileInfo[i].fullName, name, lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
dirButtons.push_back(new GameButton(GameButtonType::Dir, fileInfo[i].fullName, lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
if (fileInfo[i].fullName.IsNetworkRoot()) {
dirButtons.back()->SetAsAbsolute();
}
}
} else {
gameButtons.push_back(new GameButton(fileInfo[i].fullName, lastViewMode_, new UI::LinearLayoutParams(gridStyle ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT)));
gameButtons.push_back(new GameButton(GameButtonType::Game, fileInfo[i].fullName, lastViewMode_, new UI::LinearLayoutParams(gridStyle ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT)));
}
}
@@ -1190,8 +479,7 @@ void GameBrowser::Refresh() {
if (!fileInfo.empty()) {
for (size_t i = 0; i < fileInfo.size(); i++) {
if (!fileInfo[i].isDirectory) {
GameButton *b = new GameButton(fileInfo[i].fullName, lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT));
b->SetHoldEnabled(false);
GameButton *b = new GameButton(GameButtonType::Other, fileInfo[i].fullName, lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT));
gameButtons.push_back(b);
}
}
@@ -1199,19 +487,20 @@ void GameBrowser::Refresh() {
}
}
DirButton *upButton = nullptr;
GameButton *upButton = nullptr;
if (browseFlags_ & BrowseFlags::NAVIGATE) {
if (path_.CanNavigateUp()) {
upButton = gameList_->Add(new DirButton(Path(".."), lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
upButton = gameList_->Add(new GameButton(GameButtonType::Dir, Path(".."), lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
upButton->OnClick.Handle(this, &GameBrowser::NavigateClick);
}
// Add any pinned paths before other directories.
auto pinnedPaths = GetPinnedPaths();
for (const auto &pinnedPath : pinnedPaths) {
DirButton *pinnedDir = gameList_->Add(new DirButton(pinnedPath, pinnedPath.GetFilename(), lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
GameButton *pinnedDir = gameList_->Add(new GameButton(GameButtonType::Dir, pinnedPath, lastViewMode_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)));
pinnedDir->OnClick.Handle(this, &GameBrowser::NavigateClick);
pinnedDir->SetPinned(true);
pinnedDir->SetAsAbsolute();
}
}
@@ -1232,7 +521,7 @@ void GameBrowser::Refresh() {
b->OnHoldClick.Handle(this, &GameBrowser::GameButtonHoldClick);
b->OnHighlight.Handle(this, &GameBrowser::GameButtonHighlight);
if (!focusPath_.empty() && b->GamePath() == focusPath_ && b->SetFocus(FocusFlags::CAUSE_RESTORE)) {
if (!focusPath_.empty() && b->GetPath() == focusPath_ && b->SetFocus(FocusFlags::CAUSE_RESTORE)) {
UI::ScrollFocusedViewIntoView();
}
}
@@ -1250,6 +539,7 @@ void GameBrowser::Refresh() {
}
// Show a button to toggle pinning at the very end.
// TODO: That's not very friendly.
if ((browseFlags_ & BrowseFlags::PIN) && !path_.GetPath().empty()) {
std::string caption = ""; // IsCurrentPathPinned() ? "-" : "+";
if (!gridStyle) {
@@ -1317,16 +607,22 @@ std::vector<Path> GameBrowser::GetPinnedPaths() const {
}
void GameBrowser::GameButtonClick(UI::EventParams &e) {
GameButton *button = static_cast<GameButton *>(e.v);
GameButton *button = dynamic_cast<GameButton *>(e.v);
if (!button) {
return;
}
UI::EventParams e2{};
e2.s = button->GamePath().ToString();
e2.s = button->GetPath().ToString();
OnChoice.Trigger(e2);
}
void GameBrowser::GameButtonHoldClick(UI::EventParams &e) {
GameButton *button = static_cast<GameButton *>(e.v);
GameButton *button = dynamic_cast<GameButton *>(e.v);
if (!button) {
return;
}
UI::EventParams e2{};
e2.s = button->GamePath().ToString();
e2.s = button->GetPath().ToString();
OnHoldChoice.Trigger(e2);
}
@@ -1335,15 +631,18 @@ void GameBrowser::GameButtonHighlight(UI::EventParams &e) {
}
void GameBrowser::NavigateClick(UI::EventParams &e) {
DirButton *button = static_cast<DirButton *>(e.v);
GameButton *button = dynamic_cast<GameButton *>(e.v);
if (!button) {
return;
}
Path text = button->GetPath();
if (button->PathAbsolute()) {
if (button->NavigateAsAbsolute()) {
path_.SetPath(text);
} else if (text.ToString() == "..") {
NavigateUp();
return;
} else {
path_.Navigate(text.ToString());
path_.Navigate(text.GetFilename());
}
focusFirstItem_ = true;
PathChanged();
+462
View File
@@ -0,0 +1,462 @@
// Copyright (c) 2012- 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 <algorithm>
#include <cmath>
#include "Common/Data/Text/I18n.h"
#include "Common/Math/curves.h"
#include "Common/StringUtils.h"
#include "Common/TimeUtil.h"
#include "Common/UI/Context.h"
#include "Core/Config.h"
#include "Core/Loaders.h"
#include "Core/Util/VideoPlayer.h"
#include "UI/GameButton.h"
#include "UI/GameInfoCache.h"
#include "UI/GamePacker.h"
// Where the text starts in the list modes.
static float ListTextX(GameBrowserViewMode viewMode) {
if (viewMode == GameBrowserViewMode::LARGE_LIST) {
return LARGE_LIST_LEFT_PAD + LARGE_LIST_ICON_W * g_Config.fGameGridScale + 20.0f;
}
return 150.0f;
}
// Space to the right of the text in the list modes, for the info button.
static float ListTextRightMargin(GameBrowserViewMode viewMode) {
if (viewMode == GameBrowserViewMode::LARGE_LIST) {
return LARGE_LIST_INFO_SIZE + LARGE_LIST_PAD * 2;
}
return 0.0f;
}
void GameButton::UpdateVideo(const std::shared_ptr<GameInfo> &ginfo, bool wantVideo) {
if (!wantVideo) {
video_.reset();
return;
}
if (video_ || !ginfo->Ready(GameInfoFlags::ICON1_PMF)) {
return;
}
std::string data;
{
std::lock_guard<std::mutex> guard(ginfo->lock);
data = ginfo->icon1pmf;
}
if (!data.empty()) {
video_ = std::make_unique<PMFVideoTexture>(std::move(data));
videoStart_ = Instant::Now();
}
}
// These are the built-in icons used for file types that don't have one (or simply files missing one).
static ImageID ChooseGameIcon(const std::shared_ptr<GameInfo> &ginfo) {
if (ginfo->GetFilePath().Type() == PathType::HTTP) {
return ginfo->fileType == IdentifiedFileType::UNKNOWN ? ImageID("I_LINK_OUT_QUESTION") : ImageID("I_LINK_OUT");
}
switch (ginfo->fileType) {
case IdentifiedFileType::PSP_ELF:
if (ginfo->region == GameRegion::VSH) {
return ImageID::invalid(); // it has a (preset) icon.
}
// The XMB is an ELF too, but has an icon.
return ImageID("I_APP");
case IdentifiedFileType::UNKNOWN_ELF: return ImageID("I_APP"); break;
case IdentifiedFileType::PPSSPP_GE_DUMP: return ImageID("I_DISPLAY"); break;
case IdentifiedFileType::PSX_ISO:
case IdentifiedFileType::PSP_PS1_PBP: return ImageID("I_PSX_ISO"); break;
case IdentifiedFileType::PS2_ISO: return ImageID("I_PS2_ISO"); break;
case IdentifiedFileType::PS3_ISO: return ImageID("I_PS3_ISO"); break;
case IdentifiedFileType::PSP_UMD_VIDEO_ISO: return ImageID("I_UMD_VIDEO_ISO"); break;
case IdentifiedFileType::UNKNOWN_ISO: return ImageID("I_UNKNOWN_ISO"); break;
case IdentifiedFileType::PPSSPP_SAVESTATE:
case IdentifiedFileType::ERROR_IDENTIFYING:
case IdentifiedFileType::UNKNOWN_BIN: return ImageID("I_FILE"); break;
case IdentifiedFileType::ARCHIVE_ZIP: return ImageID("I_ARCHIVE_ZIP"); break;
case IdentifiedFileType::ARCHIVE_7Z: return ImageID("I_ARCHIVE_7Z"); break;
case IdentifiedFileType::ARCHIVE_RAR: return ImageID("I_ARCHIVE_RAR"); break;
case IdentifiedFileType::PSP_PKG: return ImageID("I_FOLDER_UPLOAD"); break;
case IdentifiedFileType::NORMAL_DIRECTORY: return ImageID("I_FOLDER"); break;
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_ISO_NP:
return ImageID("I_APP"); // This will practically always be replaced by a real icon.
default:
return ImageID::invalid();
}
}
void GameButton::GetContentDimensions(const UIContext &dc, float &w, float &h) const {
switch (viewMode_) {
case GameBrowserViewMode::GRID:
w = 144 * g_Config.fGameGridScale;
h = 80 * g_Config.fGameGridScale;
break;
case GameBrowserViewMode::LARGE_LIST:
w = 500;
if (type_ == GameButtonType::Dir) {
h = UI::ITEM_HEIGHT;
} else {
h = LARGE_LIST_ICON_H * g_Config.fGameGridScale + LARGE_LIST_PAD * 2;
}
break;
default: // List
w = 500;
h = 56;
break;
}
}
static const ImageID regionIcons[(int)GameRegion::COUNT] = {
ImageID("I_FLAG_JP"),
ImageID("I_FLAG_US"),
ImageID("I_FLAG_EU"),
ImageID("I_FLAG_HK"),
ImageID("I_FLAG_AS"),
ImageID("I_FLAG_KO"),
};
void GameButton::Draw(UIContext &dc) {
// Like the XMB, only the selected game plays its video. Only keyboard/gamepad focus selects, since with
// touch or the mouse, getting a video to play without launching the game was too fiddly.
const bool wantVideo = viewMode_ == GameBrowserViewMode::LARGE_LIST && HasFocus() && pmf_player_available();
std::shared_ptr<GameInfo> ginfo;
std::string text;
std::string id_version;
Draw::Texture *texture = nullptr;
ImageID imageIcon;
GameRegion region = GameRegion::UNKNOWN;
IdentifiedFileType fileType = IdentifiedFileType::UNKNOWN;
if (type_ == GameButtonType::Dir) {
text = gamePath_.GetFilename();
imageIcon = ImageID(IsPinned() ? "I_FOLDER_PINNED" : "I_FOLDER");
if (gamePath_.IsNetworkRoot()) {
imageIcon = ImageID("I_NETWORK");
auto mm = GetI18NCategory(I18NCat::MAINMENU);
text = mm->T("Network");
} else if (text == "..") {
imageIcon = ImageID("I_UP_DIRECTORY");
text = "";
}
fileType = IdentifiedFileType::NORMAL_DIRECTORY;
} else {
GameInfoFlags infoFlags = GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON;
if (wantVideo) {
infoFlags |= GameInfoFlags::ICON1_PMF;
}
ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, infoFlags);
UpdateVideo(ginfo, wantVideo);
imageIcon = ChooseGameIcon(ginfo);
if (ginfo->Ready(GameInfoFlags::ICON)) {
if (ginfo->icon.texture) {
texture = ginfo->icon.texture;
// The type icon is only for when there's no picture, and would be drawn on top of it.
imageIcon = ImageID::invalid();
} else {
// No icon, but drawBackground is set. Let's show a plain icon depending on type.
imageIcon = ChooseGameIcon(ginfo);
}
} else if (gamePath_.Type() == PathType::HTTP) {
// Make sure that pending HTTP icons show something, instead of just blank.
// Maybe we should show something for other file types too.
imageIcon = ImageID("I_LINK_OUT_QUESTION");
}
text = ginfo->GetTitle();
text = ReplaceAll(text, "\n", " ");
id_version = ginfo->id_version;
region = ginfo->region;
fileType = ginfo->fileType;
if (ginfo->disc_total > 1) {
text += StringFromFormat("-DISC%d", ginfo->disc_number);
}
}
if (video_) {
Draw::Texture *frame = video_->Update(dc.GetDrawContext(), videoStart_.ElapsedSeconds());
if (frame) {
texture = frame;
}
}
u32 color = 0, shadowColor = 0;
using namespace UI;
UI::Style style = dc.GetTheme().itemStyle;
const bool rowFocused = HasFocus() && !infoSelected_;
if (rowFocused) style = dc.GetTheme().itemFocusedStyle;
if (down_) style = dc.GetTheme().itemDownStyle;
if (!IsEnabled()) style = dc.GetTheme().itemDisabledStyle;
if (viewMode_ != GameBrowserViewMode::GRID || !texture) {
Drawable bg = style.background;
dc.Draw()->Flush();
dc.RebindTexture();
dc.FillRect(bg, bounds_);
dc.Draw()->Flush();
}
// Some types we just draw a default icon for.
Bounds iconBounds = bounds_;
iconBounds.w = viewMode_ == GameBrowserViewMode::GRID ? bounds_.w : 144;
if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
iconBounds.x += LARGE_LIST_LEFT_PAD;
iconBounds.y += LARGE_LIST_PAD;
iconBounds.w = LARGE_LIST_ICON_W * g_Config.fGameGridScale;
iconBounds.h = LARGE_LIST_ICON_H * g_Config.fGameGridScale;
}
const float textX = ListTextX(viewMode_);
if (texture && ginfo) {
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(iconBounds.h * tw / th, (float)iconBounds.w);
iconBounds.x += (iconBounds.w - nw) / 2.0f;
iconBounds.w = nw;
}
int txOffset = down_ ? 4 : 0;
if (viewMode_ != GameBrowserViewMode::GRID) {
txOffset = 0;
}
Bounds overlayBounds = bounds_;
// Render the icon, all modes have this.
int dropsize = 10;
if (texture) {
if (viewMode_ == GameBrowserViewMode::LIST) {
iconBounds.x += 4;
}
if (txOffset) {
dropsize = 3;
iconBounds.y += txOffset * 2;
overlayBounds.y += txOffset * 2;
}
if (HasFocus()) {
dc.Draw()->Flush();
dc.RebindTexture();
float pulse = sin(time_now_d() * 7.0) * 0.25 + 0.8;
dc.DrawRectDropShadow(iconBounds, 10.0f, pulse, alphaMul(color, pulse));
dc.Draw()->Flush();
} else {
dc.Draw()->Flush();
dc.RebindTexture();
dc.DrawRectDropShadow(iconBounds, 8.0f, 0.7f, shadowColor);
// dc.Draw()->DrawImage4Grid(dc.GetTheme().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);
dc.Draw()->DrawTexRect(iconBounds, 0, 0, 1, 1, color);
dc.Draw()->Flush();
}
if (viewMode_ == GameBrowserViewMode::GRID && g_Config.bShowIDOnGameIcon && fileType != IdentifiedFileType::PSP_ELF && !id_version.empty()) {
std::string_view idStr = id_version;
if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) {
auto ga = GetI18NCategory(I18NCat::GAME);
idStr = ga->T("SaveData");
}
dc.SetFontScale(0.5f * g_Config.fGameGridScale, 0.5f * g_Config.fGameGridScale);
dc.DrawText(idStr, bounds_.x + 5, iconBounds.y + 1, 0xFF000000, ALIGN_TOPLEFT);
dc.DrawText(idStr, bounds_.x + 4, iconBounds.y, dc.GetTheme().infoStyle.fgColor, ALIGN_TOPLEFT);
dc.SetFontScale(1.0f, 1.0f);
}
if (viewMode_ == GameBrowserViewMode::GRID) {
if (imageIcon.isValid()) {
Bounds bounds = bounds_;
float tw, th;
dc.MeasureText(dc.GetFontStyle(), g_Config.fGameGridScale, g_Config.fGameGridScale, text, &tw, &th, 0);
dc.PushScissor(bounds);
const FontStyle *fontStyle = GetTextStyle(dc, UI::TextSize::Small);
dc.SetFontStyle(*GetTextStyle(dc, UI::TextSize::Small));
int iconSize = imageIcon == ImageID("I_UP_DIRECTORY") ? (float)dc.Draw()->GetAtlas()->getImage(imageIcon)->h : bounds.h * 0.3f;
float textWidth = 0.0f;
float textHeight = 0;
dc.MeasureTextRect(*fontStyle, 1.0f, 1.0f, text, bounds.w - 10, &textWidth, &textHeight, ALIGN_HCENTER | FLAG_WRAP_TEXT);
int totalHeight = iconSize + (int)textHeight;
const float y = std::max(0.0f, bounds.h / 2.0f - totalHeight / 2.0f);
const AtlasImage *img = dc.Draw()->GetAtlas()->getImage(imageIcon);
if (img && img->h > 0) {
dc.RebindTexture();
dc.Draw()->DrawImage(imageIcon, bounds.centerX(), bounds.y + y + 2, iconSize / (float)img->h, style.fgColor, ALIGN_TOP | ALIGN_HCENTER);
}
if (imageIcon != ImageID("I_UP_DIRECTORY") && imageIcon != ImageID("I_PIN") && imageIcon != ImageID("I_UNPIN")) {
dc.DrawTextRect(text, bounds.Inset(5, y + iconSize + 4, 5, 2), style.fgColor, ALIGN_HCENTER | FLAG_WRAP_TEXT);
}
dc.PopScissor();
dc.SetFontStyle(dc.GetTheme().uiFont);
} else if (!texture) {
// Just draw the text, centered.
dc.Draw()->Flush();
dc.PushScissor(bounds_);
dc.DrawText(title_, bounds_.x + 4, bounds_.centerY(), style.fgColor, ALIGN_VCENTER);
dc.Draw()->Flush();
dc.PopScissor();
}
} else {
Bounds bounds = bounds_;
std::string_view rightText = g_Config.bShowIDOnGameIcon ? std::string_view(id_version) : std::string_view();
float tw, th;
dc.MeasureText(dc.GetFontStyle(), 1.0f, 1.0f, text, &tw, &th, 0);
if (imageIcon.isValid()) {
const float textX = ListTextX(viewMode_);
if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
const float scale = g_Config.fGameGridScale;
dc.Draw()->DrawImage(imageIcon, bounds.x + LARGE_LIST_LEFT_PAD + LARGE_LIST_ICON_W * 0.5f * scale, bounds.centerY(), 0.88f * scale, style.fgColor, ALIGN_CENTER);
} else {
dc.Draw()->DrawImage(imageIcon, bounds.x + 72, bounds.centerY(), 0.88f, style.fgColor, ALIGN_CENTER);
}
}
float tx = textX;
int availableWidth = bounds.w - textX - ListTextRightMargin(viewMode_);
if (!rightText.empty()) {
float vw, vh;
dc.MeasureText(dc.GetFontStyle(), 0.7f, 0.7f, rightText, &vw, &vh, 0);
availableWidth -= vw + 20;
dc.SetFontScale(0.7f, 0.7f);
dc.DrawText(rightText, bounds.x + availableWidth + textX + 10, bounds.centerY(), style.fgColor, ALIGN_VCENTER);
dc.SetFontScale(1.0f, 1.0f);
}
float sineWidth = std::max(0.0f, (tw - availableWidth)) / 2.0f;
if (availableWidth < tw) {
tx -= (1.0f + sin(time_now_d() * 1.5f)) * sineWidth;
Bounds tb = bounds;
tb.x = bounds.x + textX;
tb.w = availableWidth;
dc.PushScissor(tb);
}
dc.DrawText(text, bounds.x + tx, bounds.centerY(), style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT);
if (availableWidth < tw) {
dc.PopScissor();
}
}
if (ginfo && ginfo->hasConfig && !ginfo->id.empty()) {
const AtlasImage *gearImage = dc.Draw()->GetAtlas()->getImage(ImageID("I_GEAR_SMALL"));
if (gearImage) {
if (viewMode_ == GameBrowserViewMode::GRID) {
dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x, iconBounds.y2() - gearImage->h * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x + LARGE_LIST_LEFT_PAD, iconBounds.y2() - gearImage->h * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else {
dc.Draw()->DrawImage(ImageID("I_GEAR_SMALL"), bounds_.x + 4, iconBounds.y, 1.0f);
}
}
}
const int regionIndex = (int)region;
if (g_Config.bShowRegionOnGameIcon && region != GameRegion::UNKNOWN && regionIndex >= 0 && regionIndex < (int)GameRegion::COUNT) {
const AtlasImage *regionImage = dc.Draw()->GetAtlas()->getImage(regionIcons[regionIndex]);
if (regionImage) {
if (viewMode_ == GameBrowserViewMode::GRID) {
dc.Draw()->DrawImage(regionIcons[regionIndex], bounds_.x + bounds_.w - (regionImage->w + 5) * g_Config.fGameGridScale,
iconBounds.y2() - (regionImage->h + 5) * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else if (viewMode_ == GameBrowserViewMode::LARGE_LIST) {
dc.Draw()->DrawImage(regionIcons[regionIndex], bounds_.x + LARGE_LIST_LEFT_PAD + (LARGE_LIST_ICON_W - regionImage->w - 5) * g_Config.fGameGridScale,
iconBounds.y2() - (regionImage->h + 5) * g_Config.fGameGridScale, g_Config.fGameGridScale);
} else {
dc.Draw()->DrawImage(regionIcons[regionIndex], bounds_.x + 4, iconBounds.y2() - regionImage->h - 5, 1.0f);
}
}
}
if (HasInfoButton()) {
const Bounds infoBounds = InfoButtonBounds();
Style infoStyle = HasFocus() ? dc.GetTheme().itemFocusedStyle : dc.GetTheme().itemStyle;
if (infoButtonDown_) {
infoStyle = dc.GetTheme().itemDownStyle;
dc.FillRoundRect(infoStyle.background, infoBounds, dc.GetTheme().choiceRoundRadius);
} else if (HasFocus() && infoSelected_) {
dc.FillRoundRect(infoStyle.background, infoBounds, dc.GetTheme().choiceRoundRadius);
}
dc.Draw()->DrawImage(ImageID("I_INFO"), infoBounds.centerX(), infoBounds.centerY(), 1.0f, infoStyle.fgColor, ALIGN_CENTER);
}
u32 overlayColor = 0;
if (holdEnabled_ && holdStart_ != 0.0) {
double time_held = time_now_d() - holdStart_;
overlayColor = whiteAlpha(time_held / 2.5f);
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)
overlayColor = 0x0;
}
}
}
if (overlayColor) {
dc.FillRect(Drawable(overlayColor), overlayBounds);
}
// Covers the icon while the game is being compressed. Clicking opens the game screen instead of playing.
GamePacker::Status packStatus;
if (GamePacker::IsPacking(gamePath_) && GamePacker::GetStatus(&packStatus)) {
auto ga = GetI18NCategory(I18NCat::GAME);
dc.FillRect(Drawable(0xD0505050), iconBounds);
const float scale = viewMode_ == GameBrowserViewMode::GRID || viewMode_ == GameBrowserViewMode::LARGE_LIST ? 0.8f * g_Config.fGameGridScale : 0.6f;
dc.SetFontStyle(dc.GetTheme().uiFont);
dc.SetFontScale(scale, scale);
const std::string text = std::string(ga->T("Compressing...")) + StringFromFormat("\n%d%%", (int)(packStatus.progress * 100.0f));
dc.DrawTextRectSqueeze(text, iconBounds, 0xFFFFFFFF, ALIGN_CENTER);
dc.SetFontScale(1.0f, 1.0f);
}
dc.RebindTexture();
}
std::string GameButton::DescribeText() const {
// LOW: search calls this for every game in the list, which mustn't hold up the visible ones.
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO, nullptr, GameInfoFlags::EMPTY, TaskPriority::LOW);
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO))
return "...";
auto u = GetI18NCategory(I18NCat::UI_ELEMENTS);
return ginfo->GetTitle();
}
+269
View File
@@ -0,0 +1,269 @@
// Copyright (c) 2012- 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 <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include "Common/File/Path.h"
#include "Common/TimeUtil.h"
#include "Common/UI/View.h"
#include "Common/UI/Root.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "UI/MiscViews.h"
class GameInfo;
// Icon dimensions in the large list, and its padding around the icon.
static constexpr float LARGE_LIST_ICON_W = 144.0f;
static constexpr float LARGE_LIST_ICON_H = 80.0f;
static constexpr float LARGE_LIST_PAD = 6.0f;
static constexpr float LARGE_LIST_LEFT_PAD = 11.0f;
static constexpr float LARGE_LIST_INFO_SIZE = 64.0f;
enum class GameButtonType {
Game,
Other,
Dir,
};
class GameButton : public UI::Clickable {
public:
GameButton(GameButtonType type, const Path &gamePath, GameBrowserViewMode viewMode, UI::LayoutParams *layoutParams = nullptr)
: UI::Clickable(layoutParams), type_(type), viewMode_(viewMode), gamePath_(gamePath) {}
void Draw(UIContext &dc) override;
std::string DescribeText() const override;
std::string SearchAlias() const override {
// Without the extension, or searching for "iso" would match every ISO. Only three or more
// letters counts as one, so a folder like "Game v1.2" keeps its version number.
std::string filename = gamePath_.GetFilename();
const std::string ext = gamePath_.GetFileExtension();
if (ext.size() >= 4 && ext.size() <= filename.size() && std::all_of(ext.begin() + 1, ext.end(), [](char c) { return c >= 'a' && c <= 'z'; })) {
filename.resize(filename.size() - ext.size());
}
return filename;
}
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
bool IsHoldEnabled() const {
return type_ == GameButtonType::Game;
}
bool Touch(const TouchInput &input) override {
hovering_ = bounds_.Contains(input.x, input.y);
if (HasInfoButton() && TouchInfoButton(input)) {
return true;
}
const bool retval = UI::Clickable::Touch(input);
if (hovering_ && (input.flags & TouchInputFlags::DOWN)) {
holdStart_ = time_now_d();
}
if (input.flags & TouchInputFlags::UP) {
holdStart_ = 0;
}
return retval;
}
bool Key(const KeyInput &key) override {
bool showInfo = false;
if (HasFocus() && UI::IsInfoKey(key)) {
// If it's the button that's mapped to triangle, then show the info.
if (key.flags & KeyInputFlags::DOWN) {
infoKeyPressed_ = true;
}
if ((key.flags & KeyInputFlags::UP) && infoKeyPressed_) {
showInfo = true;
infoKeyPressed_ = false;
}
} 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 & KeyInputFlags::DOWN) {
showInfoPressed_ = true;
}
if ((key.flags & KeyInputFlags::UP) && showInfoPressed_) {
showInfo = true;
showInfoPressed_ = false;
}
}
if (showInfo) {
TriggerOnHoldClick();
return true;
}
if (infoSelected_ && HasFocus() && UI::IsAcceptKey(key)) {
if (key.flags & KeyInputFlags::DOWN) {
infoButtonDown_ = true;
}
if ((key.flags & KeyInputFlags::UP) && infoButtonDown_) {
infoButtonDown_ = false;
TriggerOnHoldClick();
}
return true;
}
// Holding the accept button works like a long press, so a gamepad can do it too.
if (IsHoldEnabled() && HasFocus() && UI::IsAcceptKey(key) && !(key.flags & KeyInputFlags::IS_REPEAT)) {
if (key.flags & KeyInputFlags::DOWN) {
holdStart_ = time_now_d();
}
if (key.flags & KeyInputFlags::UP) {
holdStart_ = 0.0;
}
}
return Clickable::Key(key);
}
void Update() override {
// Hold button for 1.5 seconds to launch the game options
if (IsHoldEnabled() && holdStart_ != 0.0 && holdStart_ < time_now_d() - 1.5) {
TriggerOnHoldClick();
}
}
void FocusChanged(UI::FocusFlags focusFlags) override {
UI::Clickable::FocusChanged(focusFlags);
if (focusFlags & UI::FocusFlags::LOST_FOCUS) {
// Don't let a held accept button carry over from a button we've left.
holdStart_ = 0.0;
// Moving to another game selects the game, not its info button.
infoSelected_ = false;
infoButtonDown_ = false;
}
TriggerOnHighlight(focusFlags);
}
// Left and right move between the game and its info button, so up and down still go from game to game.
bool CanMoveFocus(UI::FocusMove dir) const override {
if (!HasInfoButton()) {
return true;
}
// We handle these moves ourselves, so MoveFocus won't play the sound for them.
if (dir == UI::FocusMove::RIGHT && !infoSelected_) {
infoSelected_ = true;
UI::PlayUISound(UI::UISound::SELECT);
return false;
}
if (dir == UI::FocusMove::LEFT && infoSelected_) {
infoSelected_ = false;
UI::PlayUISound(UI::UISound::SELECT);
return false;
}
return true;
}
void DeviceLost() override {
if (video_) {
video_->DeviceLost();
}
}
// Previously in DirButton
// This only determines if the little pin icon is shown.
void SetPinned(bool pin) { pinned_ = pin; }
bool IsPinned() const { return pinned_; }
void SetAsAbsolute() { absolute_ = true; }
bool NavigateAsAbsolute() const { return absolute_; }
const Path &GetPath() const { return gamePath_; }
UI::Event OnHoldClick;
UI::Event OnHighlight;
private:
void TriggerOnHoldClick() {
holdStart_ = 0.0;
UI::EventParams e{};
e.v = this;
e.s = gamePath_.ToString();
down_ = false;
OnHoldClick.Trigger(e);
}
void TriggerOnHighlight(UI::FocusFlags focusFlags) {
UI::EventParams e{};
e.v = this;
e.s = gamePath_.ToString();
e.a = (u32)focusFlags;
OnHighlight.Trigger(e);
}
void UpdateVideo(const std::shared_ptr<GameInfo> &ginfo, bool wantVideo);
// The large list has room for an info button on each line, doing the same as a long press.
bool HasInfoButton() const {
return viewMode_ == GameBrowserViewMode::LARGE_LIST && type_ == GameButtonType::Game;
}
Bounds InfoButtonBounds() const {
return Bounds(bounds_.x2() - LARGE_LIST_INFO_SIZE - LARGE_LIST_PAD, bounds_.centerY() - LARGE_LIST_INFO_SIZE * 0.5f, LARGE_LIST_INFO_SIZE, LARGE_LIST_INFO_SIZE);
}
// Returns true if the info button took the input.
bool TouchInfoButton(const TouchInput &input) {
if ((input.flags & TouchInputFlags::MOUSE) && (input.buttons & 1) == 0) {
return false;
}
const bool inside = InfoButtonBounds().Contains(input.x, input.y);
if (input.flags & TouchInputFlags::DOWN) {
infoButtonDown_ = inside;
}
if (!infoButtonDown_) {
return false;
}
if (input.flags & TouchInputFlags::UP) {
infoButtonDown_ = false;
if (inside && !(input.flags & TouchInputFlags::CANCEL)) {
TriggerOnHoldClick();
}
}
return true;
}
const GameBrowserViewMode viewMode_;
const Path gamePath_;
std::string title_;
// The ICON1.PMF video, played in place of the icon in the large list, like the XMB does.
std::unique_ptr<PMFVideoTexture> video_;
Instant videoStart_;
double holdStart_ = 0.0;
bool holdEnabled_ = true;
bool showInfoPressed_ = false;
bool infoKeyPressed_ = false;
bool hovering_ = false;
bool infoButtonDown_ = false;
// The info button has the keyboard focus, rather than the game. Changed from CanMoveFocus, hence mutable.
mutable bool infoSelected_ = false;
// Previously in DirButton
bool pinned_ = false;
bool absolute_ = false;
GameButtonType type_;
};
// Also used for the folder buttons.
void DrawIconWithTextGrid(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, const UI::Style &style);
void DrawIconWithTextList(UIContext &dc, ImageID image, std::string_view text, const Bounds &bounds, GameBrowserViewMode viewMode, const UI::Style &style, std::string_view rightText = {});
+1 -1
View File
@@ -67,7 +67,7 @@ TopBar::TopBar(const UIContext &ctx, TopBarFlags flags, std::string_view title,
}
if (!title.empty()) {
TextView *titleView = Add(new TextView(title, ALIGN_VCENTER, false, new LinearLayoutParams(1.0f, Gravity::G_VCENTER)));
TextView *titleView = Add(new TextView(title, ALIGN_VCENTER | FLAG_ELLIPSIZE_TEXT, false, new LinearLayoutParams(1.0f, Gravity::G_VCENTER)));
titleView->SetTextColor(ctx.GetTheme().itemStyle.fgColor);
titleView->SetBig(true);
if (flags & TopBarFlags::DropShadowTitle) {
+1 -1
View File
@@ -83,7 +83,7 @@ void copyDeepLinkForPath(std::string_view filePath);
void ShowMessageAfterSaveStateAction(SaveState::Status status, std::string_view message, std::string_view metadata) {
if (!message.empty()) {
g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR,
message, metadata, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);
message, metadata, status == SaveState::Status::SUCCESS ? 2.0 : 5.0, "savestateaction");
}
}
+2
View File
@@ -44,6 +44,7 @@
<ClCompile Include="EmuScreen.cpp" />
<ClCompile Include="ImposeHome.cpp" />
<ClCompile Include="GameBrowser.cpp" />
<ClCompile Include="GameButton.cpp" />
<ClCompile Include="GameInfoCache.cpp" />
<ClCompile Include="GamepadEmu.cpp" />
<ClCompile Include="GameScreen.cpp" />
@@ -118,6 +119,7 @@
<ClInclude Include="EmuScreen.h" />
<ClInclude Include="ImposeHome.h" />
<ClInclude Include="GameBrowser.h" />
<ClInclude Include="GameButton.h" />
<ClInclude Include="GameInfoCache.h" />
<ClInclude Include="GamepadEmu.h" />
<ClInclude Include="GameScreen.h" />
+6
View File
@@ -196,6 +196,9 @@
<ClCompile Include="GameBrowser.cpp">
<Filter>Views</Filter>
</ClCompile>
<ClCompile Include="GameButton.cpp">
<Filter>Views</Filter>
</ClCompile>
<ClCompile Include="LoadStateConfirmScreen.cpp" />
</ItemGroup>
<ItemGroup>
@@ -372,6 +375,9 @@
<ClInclude Include="GameBrowser.h">
<Filter>Views</Filter>
</ClInclude>
<ClInclude Include="GameButton.h">
<Filter>Views</Filter>
</ClInclude>
<ClInclude Include="LoadStateConfirmScreen.h" />
</ItemGroup>
<ItemGroup>
+2
View File
@@ -103,6 +103,7 @@
<ClInclude Include="..\..\UI\EmuScreen.h" />
<ClInclude Include="..\..\UI\ImposeHome.h" />
<ClInclude Include="..\..\UI\GameBrowser.h" />
<ClInclude Include="..\..\UI\GameButton.h" />
<ClInclude Include="..\..\UI\GameInfoCache.h" />
<ClInclude Include="..\..\UI\GamepadEmu.h" />
<ClInclude Include="..\..\UI\GameScreen.h" />
@@ -170,6 +171,7 @@
<ClCompile Include="..\..\UI\EmuScreen.cpp" />
<ClCompile Include="..\..\UI\ImposeHome.cpp" />
<ClCompile Include="..\..\UI\GameBrowser.cpp" />
<ClCompile Include="..\..\UI\GameButton.cpp" />
<ClCompile Include="..\..\UI\GameInfoCache.cpp" />
<ClCompile Include="..\..\UI\GamepadEmu.cpp" />
<ClCompile Include="..\..\UI\GameScreen.cpp" />
+2
View File
@@ -188,6 +188,7 @@
<Filter>Screens</Filter>
</ClCompile>
<ClCompile Include="..\..\UI\GameBrowser.cpp" />
<ClCompile Include="..\..\UI\GameButton.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -353,6 +354,7 @@
<Filter>Screens</Filter>
</ClInclude>
<ClInclude Include="..\..\UI\GameBrowser.h" />
<ClInclude Include="..\..\UI\GameButton.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="ImDebugger">
+1
View File
@@ -1067,6 +1067,7 @@ LOCAL_SRC_FILES := \
$(SRC)/UI/EmuScreen.cpp \
$(SRC)/UI/ImposeHome.cpp \
$(SRC)/UI/GameBrowser.cpp \
$(SRC)/UI/GameButton.cpp \
$(SRC)/UI/MainScreen.cpp \
$(SRC)/UI/TabbedDialogScreen.cpp \
$(SRC)/UI/SettingsPage.cpp \