diff --git a/Common/Math/geom2d.h b/Common/Math/geom2d.h index c063ca0d60..eb4654e6b7 100644 --- a/Common/Math/geom2d.h +++ b/Common/Math/geom2d.h @@ -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; diff --git a/Common/System/Request.cpp b/Common/System/Request.cpp index 4003318dea..6099071dbe 100644 --- a/Common/System/Request.cpp +++ b/Common/System/Request.cpp @@ -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::Entries() { @@ -27,7 +35,12 @@ std::vector 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::ProgressBars() { + std::lock_guard 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 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 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"; diff --git a/Common/System/System.h b/Common/System/System.h index 943145e28a..7ffea0b52f 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -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 Entries(); + std::vector ProgressBars(); private: std::vector entries_; + std::vector bars_; std::mutex mutex_; }; diff --git a/Core/FileSystems/BlockDevices.cpp b/Core/FileSystems/BlockDevices.cpp index ed5c06e47d..2040c4f441 100644 --- a/Core/FileSystems/BlockDevices.cpp +++ b/Core/FileSystems/BlockDevices.cpp @@ -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 guard(mutex_); MAC_KEY mkey; diff --git a/Core/FileSystems/BlockDevices.h b/Core/FileSystems/BlockDevices.h index 3268b27fa7..3575d8cded 100644 --- a/Core/FileSystems/BlockDevices.h +++ b/Core/FileSystems/BlockDevices.h @@ -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; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index df054e4a0d..3afe61abc3 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -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) { diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 741fe66888..7cfc1c6fe3 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -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. diff --git a/UI/OnScreenDisplay.cpp b/UI/OnScreenDisplay.cpp index 0fe24906ba..dcf68d2778 100644 --- a/UI/OnScreenDisplay.cpp +++ b/UI/OnScreenDisplay.cpp @@ -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 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 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. }