Merge pull request #17607 from hrydgard/overlay-progress-bars

Overlay: Support progress bars and "extra text" in messages
This commit is contained in:
Henrik Rydgård 2023-06-21 08:01:16 +02:00 committed by GitHub
commit 14586d6125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 189 additions and 20 deletions

View File

@ -72,6 +72,9 @@ struct Bounds {
Bounds Offset(float xAmount, float yAmount) const {
return Bounds(x + xAmount, y + yAmount, w, h);
}
Bounds Inset(float left, float top, float right, float bottom) {
return Bounds(x + left, y + top, w - left - right, h - bottom - top);
}
float x;
float y;

View File

@ -20,6 +20,14 @@ void OnScreenDisplay::Update() {
iter++;
}
}
for (auto iter = bars_.begin(); iter != bars_.end(); ) {
if (now >= iter->endTime) {
iter = bars_.erase(iter);
} else {
iter++;
}
}
}
std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
@ -27,7 +35,12 @@ std::vector<OnScreenDisplay::Entry> OnScreenDisplay::Entries() {
return entries_; // makes a copy.
}
void OnScreenDisplay::Show(OSDType type, const std::string &text, float duration_s, const char *id) {
std::vector<OnScreenDisplay::ProgressBar> OnScreenDisplay::ProgressBars() {
std::lock_guard<std::mutex> guard(mutex_);
return bars_; // makes a copy.
}
void OnScreenDisplay::Show(OSDType type, const std::string &text, const std::string &text2, float duration_s, const char *id) {
// Automatic duration based on type.
if (duration_s <= 0.0f) {
switch (type) {
@ -55,7 +68,9 @@ void OnScreenDisplay::Show(OSDType type, const std::string &text, float duration
Entry msg = *iter;
msg.endTime = now + duration_s;
msg.text = text;
msg.text2 = text2;
msg.type = type;
// Move to top (should we? maybe not?)
entries_.erase(iter);
entries_.insert(entries_.begin(), msg);
return;
@ -65,6 +80,7 @@ void OnScreenDisplay::Show(OSDType type, const std::string &text, float duration
Entry msg;
msg.text = text;
msg.text2 = text2;
msg.endTime = now + duration_s;
msg.type = type;
msg.id = id;
@ -76,6 +92,42 @@ void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float durat
Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s);
}
void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress) {
std::lock_guard<std::mutex> guard(mutex_);
double now = time_now_d();
bool found = false;
for (auto &bar : bars_) {
if (bar.id == id) {
bar.minValue = minValue;
bar.maxValue = maxValue;
bar.progress = progress;
bar.message = message;
bar.endTime = now + 60.0; // Nudge the progress bar to keep it shown.
return;
}
}
ProgressBar bar;
bar.id = id;
bar.message = std::move(message);
bar.minValue = minValue;
bar.maxValue = maxValue;
bar.progress = progress;
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
bars_.push_back(bar);
}
void OnScreenDisplay::RemoveProgressBar(std::string id, float fadeout_s) {
std::lock_guard<std::mutex> guard(mutex_);
for (auto iter = bars_.begin(); iter != bars_.end(); iter++) {
if (iter->id == id) {
iter->progress = iter->maxValue;
iter->endTime = time_now_d() + (double)fadeout_s;
break;
}
}
}
const char *RequestTypeAsString(SystemRequestType type) {
switch (type) {
case SystemRequestType::INPUT_TEXT_MODAL: return "INPUT_TEXT_MODAL";

View File

@ -228,7 +228,10 @@ enum class OSDType {
class OnScreenDisplay {
public:
// If you specify 0.0f as duration, a duration will be chosen automatically depending on type.
void Show(OSDType type, const std::string &message, float duration_s = 0.0f, const char *id = nullptr);
void Show(OSDType type, const std::string &text, float duration_s = 0.0f, const char *id = nullptr) {
Show(type, text, "", duration_s, id);
}
void Show(OSDType type, const std::string &text, const std::string &text2, float duration_s = 0.0f, const char *id = nullptr);
void ShowOnOff(const std::string &message, bool on, float duration_s = 0.0f);
bool IsEmpty() const { return entries_.empty(); } // Shortcut to skip rendering.
@ -236,18 +239,35 @@ public:
// Call this every frame, cleans up old entries.
void Update();
// Progress bar controls
// Set is both create and update.
void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress);
void RemoveProgressBar(std::string id, float fadeout_s);
struct Entry {
OSDType type;
std::string text;
std::string text2;
const char *id;
double endTime;
double duration;
float progress;
};
struct ProgressBar {
std::string id;
std::string message;
int minValue;
int maxValue;
int progress;
double endTime;
};
std::vector<Entry> Entries();
std::vector<ProgressBar> ProgressBars();
private:
std::vector<Entry> entries_;
std::vector<ProgressBar> bars_;
std::mutex mutex_;
};

View File

@ -73,13 +73,13 @@ u32 BlockDevice::CalculateCRC(volatile bool *cancel) {
void BlockDevice::NotifyReadError() {
auto err = GetI18NCategory(I18NCat::ERRORS);
if (!reportedError_) {
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Game disc read error - ISO corrupt"), 6.0f);
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Game disc read error - ISO corrupt"), fileLoader_->GetPath().ToVisualString(), 6.0f);
reportedError_ = true;
}
}
FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)
: fileLoader_(fileLoader) {
: BlockDevice(fileLoader) {
filesize_ = fileLoader->FileSize();
}
@ -137,7 +137,7 @@ typedef struct ciso_header
static const u32 CSO_READ_BUFFER_SIZE = 256 * 1024;
CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)
: fileLoader_(fileLoader)
: BlockDevice(fileLoader)
{
// CISO format is fairly simple, but most tools do not write the header_size.
@ -382,7 +382,7 @@ bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
}
NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FileLoader *fileLoader)
: fileLoader_(fileLoader)
: BlockDevice(fileLoader)
{
std::lock_guard<std::mutex> guard(mutex_);
MAC_KEY mkey;

View File

@ -32,6 +32,7 @@ class FileLoader;
class BlockDevice {
public:
BlockDevice(FileLoader *fileLoader) : fileLoader_(fileLoader) {}
virtual ~BlockDevice() {}
virtual bool ReadBlock(int blockNumber, u8 *outPtr, bool uncached = false) = 0;
virtual bool ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
@ -51,6 +52,7 @@ public:
void NotifyReadError();
protected:
FileLoader *fileLoader_;
bool reportedError_ = false;
};
@ -64,7 +66,6 @@ public:
bool IsDisc() override { return true; }
private:
FileLoader *fileLoader_;
u32 *index;
u8 *readBuffer;
u8 *zlibBuffer;
@ -88,7 +89,6 @@ public:
bool IsDisc() override { return true; }
private:
FileLoader *fileLoader_;
u64 filesize_;
};
@ -113,7 +113,6 @@ public:
bool IsDisc() override { return false; }
private:
FileLoader *fileLoader_;
static std::mutex mutex_;
u32 lbaSize;

View File

@ -804,7 +804,7 @@ void SystemInfoScreen::CreateTabs() {
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Warning")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.Show(OSDType::MESSAGE_WARNING, si->T("Warning"));
g_OSD.Show(OSDType::MESSAGE_WARNING, si->T("Warning"), "Some\nAdditional\nDetail");
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Info")))->OnClick.Add([&](UI::EventParams &) {
@ -815,6 +815,24 @@ void SystemInfoScreen::CreateTabs() {
g_OSD.Show(OSDType::MESSAGE_SUCCESS, si->T("Success"));
return UI::EVENT_DONE;
});
internals->Add(new ItemHeader(si->T("Progress tests")));
internals->Add(new Choice(si->T("30%")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("100%")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("N/A%")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Clear")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.RemoveProgressBar("testprogress", 0.25f);
return UI::EVENT_DONE;
});
}
void AddressPromptScreen::CreatePopupContents(UI::ViewGroup *parent) {

View File

@ -229,6 +229,8 @@ bool EmuScreen::bootAllowStorage(const Path &filename) {
}
void EmuScreen::bootGame(const Path &filename) {
auto sc = GetI18NCategory(I18NCat::SCREEN);
if (PSP_IsRebooting())
return;
if (PSP_IsInited()) {
@ -259,8 +261,6 @@ void EmuScreen::bootGame(const Path &filename) {
if (!bootAllowStorage(filename))
return;
auto sc = GetI18NCategory(I18NCat::SCREEN);
invalid_ = true;
// We don't want to boot with the wrong game specific config, so wait until info is ready.

View File

@ -40,9 +40,19 @@ ImageID GetOSDIcon(OSDType type) {
static const float iconSize = 36.0f;
static const float extraTextScale = 0.7f;
// Align only matters here for the ASCII-only flag.
static void MeasureOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, int align, float *width, float *height) {
static void MeasureOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, int align, float *width, float *height, float *height1) {
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, entry.text.c_str(), width, height, align);
*height1 = *height;
float width2 = 0.0f, height2 = 0.0f;
if (!entry.text2.empty()) {
dc.MeasureText(dc.theme->uiFont, extraTextScale, extraTextScale, entry.text2.c_str(), &width2, &height2, align);
*width = std::max(*width, width2);
*height += 5.0f + height2;
}
if (!GetOSDIcon(entry.type).isInvalid()) {
*width += iconSize + 5.0f;
@ -52,7 +62,7 @@ static void MeasureOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry,
*height = std::max(*height, iconSize + 5.0f);
}
static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, Bounds bounds, int align, float alpha) {
static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, Bounds bounds, float height1, int align, float alpha) {
UI::Drawable background = UI::Drawable(colorAlpha(GetOSDBackgroundColor(entry.type), alpha));
uint32_t foreGround = whiteAlpha(alpha);
@ -74,7 +84,61 @@ static void RenderOSDEntry(UIContext &dc, const OnScreenDisplay::Entry &entry, B
bounds.w -= iconSize + 5.0f;
}
dc.DrawTextShadowRect(entry.text.c_str(), bounds, colorAlpha(0xFFFFFFFF, alpha), (align & FLAG_DYNAMIC_ASCII) | ALIGN_CENTER);
dc.DrawTextShadowRect(entry.text.c_str(), bounds.Inset(0.0f, 1.0f, 0.0f, 0.0f), colorAlpha(0xFFFFFFFF, alpha), (align & FLAG_DYNAMIC_ASCII));
if (!entry.text2.empty()) {
Bounds bottomTextBounds = bounds.Inset(3.0f, height1 + 5.0f, 3.0f, 3.0f);
UI::Drawable backgroundDark = UI::Drawable(colorAlpha(darkenColor(GetOSDBackgroundColor(entry.type)), alpha));
dc.FillRect(backgroundDark, bottomTextBounds);
dc.SetFontScale(extraTextScale, extraTextScale);
dc.DrawTextRect(entry.text2.c_str(), bottomTextBounds, colorAlpha(0xFFFFFFFF, alpha), (align & FLAG_DYNAMIC_ASCII) | ALIGN_LEFT);
dc.SetFontScale(1.0f, 1.0f);
}
}
static void MeasureOSDProgressBar(UIContext &dc, const OnScreenDisplay::ProgressBar &bar, float *width, float *height) {
*height = 36;
*width = 450.0f;
}
static void RenderOSDProgressBar(UIContext &dc, const OnScreenDisplay::ProgressBar &entry, Bounds bounds, int align, float alpha) {
uint32_t foreGround = whiteAlpha(alpha);
Bounds shadowBounds = bounds.Expand(10.0f);
dc.Draw()->DrawImage4Grid(dc.theme->dropShadow4Grid, shadowBounds.x, shadowBounds.y + 4.0f, shadowBounds.x2(), shadowBounds.y2(), alphaMul(0xFF000000, 0.9f * alpha), 1.0f);
uint32_t backgroundColor = colorAlpha(0x806050, alpha);
uint32_t progressBackgroundColor = colorAlpha(0xa08070, alpha);
if (entry.maxValue > entry.minValue) {
// Normal progress bar
UI::Drawable background = UI::Drawable(backgroundColor);
UI::Drawable progressBackground = UI::Drawable(progressBackgroundColor);
float ratio = (float)(entry.progress - entry.minValue) / (float)entry.maxValue;
Bounds boundLeft = bounds;
Bounds boundRight = bounds;
boundLeft.w *= ratio;
boundRight.x += ratio * boundRight.w;
boundRight.w *= (1.0f - ratio);
dc.FillRect(progressBackground, boundLeft);
dc.FillRect(background, boundRight);
} else {
// Indeterminate spinner
float alpha = cos(time_now_d() * 5.0) * 0.5f + 0.5f;
uint32_t pulse = colorBlend(backgroundColor, progressBackgroundColor, alpha);
UI::Drawable background = UI::Drawable(pulse);
dc.FillRect(background, bounds);
}
dc.SetFontStyle(dc.theme->uiFont);
dc.DrawTextShadowRect(entry.message.c_str(), bounds, colorAlpha(0xFFFFFFFF, alpha), (align & FLAG_DYNAMIC_ASCII) | ALIGN_CENTER);
}
void OnScreenMessagesView::Draw(UIContext &dc) {
@ -82,14 +146,27 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
return;
}
double now = time_now_d();
// Get height
float w, h;
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, "Wg", &w, &h);
float y = 10.0f;
// Then draw them all.
const std::vector<OnScreenDisplay::ProgressBar> bars = g_OSD.ProgressBars();
for (auto &bar : bars) {
float tw, th;
MeasureOSDProgressBar(dc, bar, &tw, &th);
Bounds b(0.0f, y, tw, th);
b.x = (bounds_.w - b.w) * 0.5f;
float alpha = Clamp((float)(bar.endTime - now) * 4.0f, 0.0f, 1.0f);
RenderOSDProgressBar(dc, bar, b, 0, alpha);
y += (b.h + 4.0f) * alpha; // including alpha here gets us smooth animations.
}
const std::vector<OnScreenDisplay::Entry> entries = g_OSD.Entries();
double now = time_now_d();
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
dc.SetFontScale(1.0f, 1.0f);
// Messages that are wider than the screen are left-aligned instead of centered.
@ -101,8 +178,8 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
align |= FLAG_DYNAMIC_ASCII;
}
float tw, th;
MeasureOSDEntry(dc, *iter, align, &tw, &th);
float tw, th, h1;
MeasureOSDEntry(dc, *iter, align, &tw, &th, &h1);
Bounds b(0.0f, y, tw, th);
@ -125,7 +202,7 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
}
float alpha = Clamp((float)(iter->endTime - now) * 4.0f, 0.0f, 1.0f);
RenderOSDEntry(dc, *iter, b, align, alpha);
RenderOSDEntry(dc, *iter, b, h1, align, alpha);
y += (b.h * scale + 4.0f) * alpha; // including alpha here gets us smooth animations.
}