Show a tiny indicator in the top left of the screen when the game is saving or loading.

This commit is contained in:
Henrik Rydgård committed 2025-07-10 15:11:59 +02:00
1 parent 028392233c
commit f410b7e6ea
8 files changed
+65 -24

No files matched your search

+10 -1
View File
@@ -69,7 +69,7 @@ void OnScreenDisplay::ClickEntry(size_t index, double now) {
}
void OnScreenDisplay::Show(OSDType type, std::string_view text, std::string_view text2, std::string_view icon, float duration_s, const char *id) {
if (text.empty()) {
if (text.empty() && type != OSDType::STATUS_ICON) {
// The user hacked the translation files to get rid of the message. Let's reward the dedication
// by skipping it entirely.
return;
@@ -335,3 +335,12 @@ void OnScreenDisplay::SetClickCallback(const char *id, void (*callback)(bool, vo
}
}
}
void OnScreenDisplay::SetFlags(const char *id, OSDMessageFlags flags) {
for (auto &ent : entries_) {
// protect against dupes.
if (ent.id == id) {
ent.flags = flags;
}
}
}
+15 -1
View File
@@ -5,6 +5,8 @@
#include <vector>
#include <mutex>
#include "Common/Common.h"
// Shows a visible message to the user.
// The default implementation in NativeApp.cpp uses our "osm" system (on screen messaging).
enum class OSDType {
@@ -29,11 +31,21 @@ enum class OSDType {
PROGRESS_BAR,
TRANSPARENT_STATUS, // Use icons from the atlas
STATUS_ICON, // Use icons from the atlas
VALUE_COUNT,
};
#undef None // X11, sigh.
enum class OSDMessageFlags {
None = 0,
SpinLeft = 1,
SpinRight = 2,
Transparent = 4,
};
ENUM_CLASS_BITOPS(OSDMessageFlags);
// Data holder for on-screen messages.
class OnScreenDisplay {
public:
@@ -78,6 +90,7 @@ public:
// Can't add an infinite number of "Show" functions, so starting to offer post-modification.
void SetClickCallback(const char *id, void (*callback)(bool, void *), void *userdata);
void SetFlags(const char *id, OSDMessageFlags flag);
struct Entry {
OSDType type;
@@ -86,6 +99,7 @@ public:
std::string iconName;
int numericID;
std::string id;
OSDMessageFlags flags;
// We could use std::function, but prefer to do it the oldschool way.
void (*clickCallback)(bool, void *);
+5
View File
@@ -352,6 +352,11 @@ void UIContext::DrawImageVGradient(ImageID image, uint32_t color1, uint32_t colo
uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2);
}
void UIContext::DrawImageRotated(ImageID atlas_image, float x, float y, float scale, float angle, uint32_t color, bool mirror_h) {
uidrawbuffer_->DrawImageRotated(atlas_image, x, y, scale, angle, color, mirror_h);
}
void UIContext::PushTransform(const UITransform &transform) {
Flush();
+1
View File
@@ -95,6 +95,7 @@ public:
void FillRect(const UI::Drawable &drawable, const Bounds &bounds);
void DrawRectDropShadow(const Bounds &bounds, float radius, float alpha, uint32_t color = 0);
void DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds);
void DrawImageRotated(ImageID atlas_image, float x, float y, float scale, float angle, uint32_t color, bool mirror_h);
// in dps, like dp_xres and dp_yres
void SetBounds(const Bounds &b) { bounds_ = b; }
+15 -8
View File
@@ -21,6 +21,7 @@
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Data/Text/I18n.h"
#include "Common/System/OSD.h"
#include "Common/File/FileUtil.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
@@ -1095,6 +1096,7 @@ int PSPSaveDialog::Update(int animSpeed)
return 0;
}
// It's kinda ugly how this uses the "global" 'display'...
void PSPSaveDialog::ExecuteIOAction() {
param.ClearSFOCache();
auto &result = param.GetPspParam()->common.result;
@@ -1229,21 +1231,26 @@ void PSPSaveDialog::ExecuteNotVisibleIOAction() {
param.ClearSFOCache();
}
static void DoExecuteIOAction(PSPSaveDialog *dialog) {
SetCurrentThreadName("SaveIO");
AndroidJNIThreadContext jniContext;
dialog->ExecuteIOAction();
}
void PSPSaveDialog::StartIOThread() {
if (ioThread.joinable()) {
WARN_LOG_REPORT(Log::sceUtility, "Starting a save io thread when one already pending, uh oh.");
ioThread.join();
}
// Show save indicator. It's strange how "display" is just as much an action as what to display.
if (display == DS_SAVE_SAVING || display == DS_LOAD_LOADING || display == DS_DELETE_DELETING) {
const bool left = display == DS_LOAD_LOADING;
g_OSD.Show(OSDType::STATUS_ICON, "", "", left ? "I_ROTATE_LEFT" : "I_ROTATE_RIGHT", 1.0f, "save_indicator");
g_OSD.SetFlags("save_indicator", (left ? OSDMessageFlags::SpinLeft : OSDMessageFlags::SpinRight) | OSDMessageFlags::Transparent);
}
ioThreadStatus = SAVEIO_PENDING;
ioThread = std::thread(&DoExecuteIOAction, this);
ioThread = std::thread([this]() {
SetCurrentThreadName("SaveIO");
AndroidJNIThreadContext jniContext;
this->ExecuteIOAction();
});
}
int PSPSaveDialog::Shutdown(bool force) {
+2 -3
View File
@@ -58,8 +58,7 @@ namespace
void SetStringFromSFO(ParamSFOData &sfoFile, const char *name, char *str, int strLength)
{
std::string value = sfoFile.GetValueString(name);
truncate_cpy(str, strLength, value.c_str());
truncate_cpy(str, strLength, sfoFile.GetValueString(name));
}
bool ReadPSPFile(const std::string &filename, u8 **data, s64 dataSize, s64 *readSize)
@@ -79,7 +78,7 @@ namespace
size_t result = pspFileSystem.ReadFile(handle, *data, dataSize);
pspFileSystem.CloseFile(handle);
if(readSize)
if (readSize)
*readSize = result;
return result != 0;
+2 -1
View File
@@ -713,7 +713,8 @@ static void ShowFpsLimitNotice() {
char temp[51];
snprintf(temp, sizeof(temp), "%d%%", (int)((float)fpsLimit / 60.0f * 100.0f));
g_OSD.Show(OSDType::TRANSPARENT_STATUS, temp, "", "I_FASTFORWARD", 1.5f, "altspeed");
g_OSD.Show(OSDType::STATUS_ICON, temp, "", "I_FASTFORWARD", 1.5f, "altspeed");
g_OSD.SetFlags("altspeed", OSDMessageFlags::Transparent);
}
void EmuScreen::onVKey(VirtKey virtualKeyCode, bool down) {
+15 -10
View File
@@ -118,12 +118,12 @@ static void MeasureOSDEntry(const UIContext &dc, const OnScreenDisplay::Entry &e
}
}
static void RenderNotice(UIContext &dc, Bounds bounds, float height1, NoticeLevel level, const std::string &text, const std::string &details, const std::string &iconName, int align, float alpha, bool transparent) {
static void RenderNotice(UIContext &dc, Bounds bounds, float height1, NoticeLevel level, const std::string &text, const std::string &details, const std::string &iconName, int align, float alpha, OSDMessageFlags flags, float timeVal) {
UI::Drawable background = UI::Drawable(colorAlpha(GetNoticeBackgroundColor(level), alpha));
uint32_t foreGround = whiteAlpha(alpha);
if (!transparent) {
if (!(flags & OSDMessageFlags::Transparent)) {
dc.DrawRectDropShadow(bounds, 12.0f, 0.7f * alpha);
dc.FillRect(background, bounds);
}
@@ -153,7 +153,13 @@ static void RenderNotice(UIContext &dc, Bounds bounds, float height1, NoticeLeve
// easily melts into the orange of warnings otherwise.
dc.FillRect(UI::Drawable(0x50000000), iconBounds.Expand(2.0f));
}
dc.DrawImageVGradient(iconID, foreGround, foreGround, Bounds(bounds.x + 2.5f, bounds.y + 2.5f, iconW, iconH));
if (flags & (OSDMessageFlags::SpinLeft | OSDMessageFlags::SpinRight)) {
const float direction = (flags & OSDMessageFlags::SpinLeft) ? -1.5f : 1.5f;
dc.DrawImageRotated(iconID, bounds.x + 2.5f + iconW * 0.5f, bounds.y + 2.5f + iconW * 0.5f, 1.0f, direction * time_now_d(), foreGround, false);
} else {
dc.DrawImageVGradient(iconID, foreGround, foreGround, Bounds(bounds.x + 2.5f, bounds.y + 2.5f, iconW, iconH));
}
}
}
@@ -168,7 +174,7 @@ static void RenderNotice(UIContext &dc, Bounds bounds, float height1, NoticeLeve
if (!details.empty()) {
Bounds bottomTextBounds = bounds.Inset(3.0f, height1 + 5.0f, 3.0f, 3.0f);
if (!transparent) {
if (!(flags & OSDMessageFlags::Transparent)) {
UI::Drawable backgroundDark = UI::Drawable(colorAlpha(darkenColor(GetNoticeBackgroundColor(level)), alpha));
dc.FillRect(backgroundDark, bottomTextBounds);
}
@@ -178,7 +184,7 @@ static void RenderNotice(UIContext &dc, Bounds bounds, float height1, NoticeLeve
dc.SetFontScale(1.0f, 1.0f);
}
static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, Bounds bounds, float height1, int align, float alpha) {
static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, Bounds bounds, float height1, int align, float alpha, float now) {
if (entry.type == OSDType::ACHIEVEMENT_UNLOCKED) {
const rc_client_achievement_t * achievement = rc_client_get_achievement_info(Achievements::GetClient(), entry.numericID);
if (achievement) {
@@ -186,8 +192,7 @@ static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, B
}
return;
} else {
bool transparent = entry.type == OSDType::TRANSPARENT_STATUS;
RenderNotice(dc, bounds, height1, GetNoticeLevel(entry.type), entry.text, entry.text2, entry.iconName, align, alpha, transparent);
RenderNotice(dc, bounds, height1, GetNoticeLevel(entry.type), entry.text, entry.text2, entry.iconName, align, alpha, entry.flags, now - entry.startTime);
}
}
@@ -309,7 +314,7 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
typeEdges[(size_t)OSDType::ACHIEVEMENT_UNLOCKED] = (ScreenEdgePosition)g_Config.iAchievementsUnlockedPos;
typeEdges[(size_t)OSDType::MESSAGE_CENTERED_WARNING] = ScreenEdgePosition::CENTER;
typeEdges[(size_t)OSDType::MESSAGE_CENTERED_ERROR] = ScreenEdgePosition::CENTER;
typeEdges[(size_t)OSDType::TRANSPARENT_STATUS] = ScreenEdgePosition::TOP_LEFT;
typeEdges[(size_t)OSDType::STATUS_ICON] = ScreenEdgePosition::TOP_LEFT;
typeEdges[(size_t)OSDType::PROGRESS_BAR] = ScreenEdgePosition::TOP_CENTER; // These only function at the top currently, needs fixing.
dc.SetFontStyle(dc.theme->uiFont);
@@ -471,7 +476,7 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
}
float alpha = Clamp((float)(entry.endTime - now) * 4.0f, 0.0f, 1.0f);
RenderOSDEntry(dc, entry, b, measuredEntry.h1, measuredEntry.align, alpha);
RenderOSDEntry(dc, entry, b, measuredEntry.h1, measuredEntry.align, alpha, now);
switch (entry.type) {
case OSDType::MESSAGE_INFO:
@@ -584,6 +589,6 @@ void NoticeView::GetContentDimensionsBySpec(const UIContext &dc, UI::MeasureSpec
void NoticeView::Draw(UIContext &dc) {
dc.PushScissor(bounds_);
RenderNotice(dc, bounds_, height1_, level_, text_, detailsText_, iconName_, 0, 1.0f, false);
RenderNotice(dc, bounds_, height1_, level_, text_, detailsText_, iconName_, 0, 1.0f, OSDMessageFlags::None, 0.0f);
dc.PopScissor();
}