From 7d9539a461c989a744ab464c387d880570e18cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 21:33:54 +0200 Subject: [PATCH 1/9] Fix multiple issues with temporary breakpoints. Prevents unnecessary jit clears on step-out. Thanks Nemoumbra. --- Core/Debugger/Breakpoints.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index 5e6849ced9..eea01ba1fa 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -216,15 +216,30 @@ void BreakpointManager::ClearTemporaryBreakPoints() { if (!anyBreakPoints_) return; + std::unique_lock guard(breakPointsMutex_); - for (int i = (int)breakPoints_.size()-1; i >= 0; --i) - { - if (breakPoints_[i].temporary) - { - breakPoints_.erase(breakPoints_.begin() + i); - Update(); + + std::vector addrsToUpdate; + + for (auto it = breakPoints_.begin(); it != breakPoints_.end(); ) { + if (it->temporary) { + addrsToUpdate.push_back(it->addr); + it = breakPoints_.erase(it); + } else { + ++it; } } + + if (addrsToUpdate.size() == 1) { + // We can use the proper mechanism to update just one address. + // If there are any temp breakpoints, there's normally just one, so this is better + // than Update(). + Update(addrsToUpdate[0]); + } else if (!addrsToUpdate.empty()) { + Update(); + } + + anyBreakPoints_ = !breakPoints_.empty(); } void BreakpointManager::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) From 1f4d3b3b04140afa132685da6accaf34184b1882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 21:46:13 +0200 Subject: [PATCH 2/9] Minor code cleanup --- Core/Debugger/Breakpoints.cpp | 36 +++++++++++++++++++---------------- Core/Debugger/Breakpoints.h | 7 ++++++- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index eea01ba1fa..886394ac37 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -200,15 +200,19 @@ void BreakpointManager::ChangeBreakPoint(u32 addr, BreakAction result) { } } -void BreakpointManager::ClearAllBreakPoints() -{ +// This is not actually called, currently. +void BreakpointManager::ClearAllBreakPoints() { if (!anyBreakPoints_) return; std::unique_lock guard(breakPointsMutex_); - if (!breakPoints_.empty()) - { + if (!breakPoints_.empty()) { + // Same strategy as ClearTemporaryBreakPoints - if there's only one, we can update just that one. + if (breakPoints_.size() == 1) { + Update(breakPoints_[0].addr); + } else { + Update(0); + } breakPoints_.clear(); - Update(); } } @@ -236,7 +240,7 @@ void BreakpointManager::ClearTemporaryBreakPoints() // than Update(). Update(addrsToUpdate[0]); } else if (!addrsToUpdate.empty()) { - Update(); + Update(0); } anyBreakPoints_ = !breakPoints_.empty(); @@ -331,7 +335,7 @@ int BreakpointManager::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, B if (!hadAny) { MemBlockOverrideDetailed(); } - Update(); + Update(0); // Memchecks are not per-address, so just update everything. return (int)memChecks_.size() - 1; } else { memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond); @@ -340,7 +344,7 @@ int BreakpointManager::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, B if (!hadAny) { MemBlockOverrideDetailed(); } - Update(); + Update(0); return (int)mc; } } @@ -356,7 +360,7 @@ void BreakpointManager::RemoveMemCheck(u32 start, u32 end) bool hadAny = anyMemChecks_.exchange(!memChecks_.empty()); if (hadAny) MemBlockReleaseDetailed(); - Update(); + Update(0); } } @@ -368,7 +372,7 @@ void BreakpointManager::ChangeMemCheck(u32 start, u32 end, MemCheckCondition con { memChecks_[mc].cond = cond; memChecks_[mc].result = result; - Update(); + Update(0); } } @@ -382,7 +386,7 @@ void BreakpointManager::ClearAllMemChecks() bool hadAny = anyMemChecks_.exchange(false); if (hadAny) MemBlockReleaseDetailed(); - Update(); + Update(0); } } @@ -394,7 +398,7 @@ void BreakpointManager::ChangeMemCheckAddCond(u32 start, u32 end, const BreakPoi memChecks_[mc].hasCondition = true; memChecks_[mc].condition = cond; // No need to update jit for a condition add/remove, they're not baked in. - Update(-1); + Update(INVALID_ADDRESS); } } @@ -404,7 +408,7 @@ void BreakpointManager::ChangeMemCheckRemoveCond(u32 start, u32 end) { if (mc != INVALID_MEMCHECK) { memChecks_[mc].hasCondition = false; // No need to update jit for a condition add/remove, they're not baked in. - Update(-1); + Update(INVALID_ADDRESS); } } @@ -421,7 +425,7 @@ void BreakpointManager::ChangeMemCheckLogFormat(u32 start, u32 end, const std::s size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].logFormat = fmt; - Update(); + Update(0); // wipe the jit. } } @@ -611,7 +615,7 @@ void BreakpointManager::Frame() { } std::lock_guard guard(breakPointsMutex_); - if (MIPSComp::jit && updateAddr_ != -1) { + if (MIPSComp::jit && updateAddr_ != INVALID_ADDRESS) { // In case this is a delay slot, clear the previous instruction too. if (updateAddr_ != 0) mipsr4k.InvalidateICache(updateAddr_ - 4, 8); @@ -619,7 +623,7 @@ void BreakpointManager::Frame() { mipsr4k.ClearJitCache(); } - if (anyMemChecks_ && updateAddr_ != -1) + if (anyMemChecks_ && updateAddr_ != INVALID_ADDRESS) UpdateCachedMemCheckRanges(); // Redraw in order to show the breakpoint. diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index ea5aa05ca7..ebdcbcb440 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -191,7 +191,8 @@ public: private: // Should be called under lock. - void Update(u32 addr = 0) { + // 0 means to clear the whole jit cache, to apply some change that has been made. + void Update(u32 addr) { needsUpdate_ = true; updateAddr_ = addr; } @@ -217,6 +218,10 @@ private: bool needsUpdate_ = true; u32 updateAddr_ = 0; + + enum { + INVALID_ADDRESS = -1 + }; }; extern BreakpointManager g_breakpoints; From 41771d536979e235bb3d3e2f409717116959b31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 22:12:23 +0200 Subject: [PATCH 3/9] Minor cleanup in TabHolder --- Common/UI/TabHolder.cpp | 21 +++++++++++++++------ Common/UI/TabHolder.h | 2 +- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index 0c16b71881..ed97ca0f82 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -145,11 +145,15 @@ bool TabHolder::SetCurrentTab(int tab, bool skipTween) { return false; } - bool created = false; - - if (tab != currentTab_) { - _dbg_assert_(tabs_[currentTab_]); // we should always have a tab to switch *from*. - created = EnsureTab(tab); + if (currentTab_ < 0 || currentTab_ >= (int)tabs_.size()) { + _dbg_assert_(false); + // No current tab, so just switch immediately. + currentTab_ = tab; + tabStrip_->SetSelection(tab, false); + if (tabs_[tab]) { + tabs_[tab]->SetVisibility(V_VISIBLE); + } + return true; } auto setupTween = [this](View *view, AnchorTranslateTween *&tween) { @@ -161,13 +165,18 @@ bool TabHolder::SetCurrentTab(int tab, bool skipTween) { return; } tween = new AnchorTranslateTween(0.15f, bezierEaseInOut); - tween->Finish.Add([&](EventParams &e) { + tween->Finish.Add([this](EventParams &e) { e.v->SetVisibility(tabs_[currentTab_] == e.v ? V_VISIBLE : V_GONE); }); view->AddTween(tween)->Persist(); }; + bool created = false; + if (tab != currentTab_) { + _dbg_assert_(tabs_[currentTab_]); // we should always have a tab to switch *from*. + created = EnsureTab(tab); + Orientation orient = Opposite(orientation_); // Direction from which the new tab will come. float dir = tab < currentTab_ ? -1.0f : 1.0f; diff --git a/Common/UI/TabHolder.h b/Common/UI/TabHolder.h index 096efbb061..f88f2946eb 100644 --- a/Common/UI/TabHolder.h +++ b/Common/UI/TabHolder.h @@ -66,7 +66,7 @@ private: TabHolderFlags flags_ = TabHolderFlags::Default; int currentTab_ = 0; std::vector tabs_; - std::vector tabTweens_; + std::vector tabTweens_; // NOTE: The tweens are actually owned by the tabs. std::vector> createFuncs_; }; From 00bfad40bd4229ab567ae3e8b10782d4f93d8b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 22:24:33 +0200 Subject: [PATCH 4/9] More refactoring in TabHolder --- Common/UI/TabHolder.cpp | 65 ++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index ed97ca0f82..dd3900d0c0 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -145,7 +145,7 @@ bool TabHolder::SetCurrentTab(int tab, bool skipTween) { return false; } - if (currentTab_ < 0 || currentTab_ >= (int)tabs_.size()) { + if (currentTab_ < 0 || currentTab_ >= (int)tabs_.size() || !tabs_[currentTab_]) { _dbg_assert_(false); // No current tab, so just switch immediately. currentTab_ = tab; @@ -156,6 +156,11 @@ bool TabHolder::SetCurrentTab(int tab, bool skipTween) { return true; } + if (tab == currentTab_) { + tabStrip_->SetSelection(tab, false); + return false; + } + auto setupTween = [this](View *view, AnchorTranslateTween *&tween) { _dbg_assert_(view != nullptr); if (!view) { @@ -171,44 +176,38 @@ bool TabHolder::SetCurrentTab(int tab, bool skipTween) { view->AddTween(tween)->Persist(); }; - bool created = false; + bool created = EnsureTab(tab); - if (tab != currentTab_) { - _dbg_assert_(tabs_[currentTab_]); // we should always have a tab to switch *from*. - created = EnsureTab(tab); + Orientation orient = Opposite(orientation_); + // Direction from which the new tab will come. + float dir = tab < currentTab_ ? -1.0f : 1.0f; - Orientation orient = Opposite(orientation_); - // Direction from which the new tab will come. - float dir = tab < currentTab_ ? -1.0f : 1.0f; + // First, setup any missing tweens. + setupTween(tabs_[currentTab_], tabTweens_[currentTab_]); + setupTween(tabs_[tab], tabTweens_[tab]); - // First, setup any missing tweens. - setupTween(tabs_[currentTab_], tabTweens_[currentTab_]); - setupTween(tabs_[tab], tabTweens_[tab]); + // Currently displayed, so let's reset it. + if (skipTween) { + tabs_[currentTab_]->SetVisibility(V_GONE); + tabTweens_[tab]->Reset(Point2D(0.0f, 0.0f)); + tabTweens_[tab]->Apply(tabs_[tab]); + } else { + tabTweens_[currentTab_]->Reset(Point2D(0.0f, 0.0f)); - // Currently displayed, so let's reset it. - if (skipTween) { - tabs_[currentTab_]->SetVisibility(V_GONE); - tabTweens_[tab]->Reset(Point2D(0.0f, 0.0f)); - tabTweens_[tab]->Apply(tabs_[tab]); + if (orient == ORIENT_HORIZONTAL) { + tabTweens_[tab]->Reset(Point2D(bounds_.w * dir, 0.0f)); + tabTweens_[currentTab_]->Divert(Point2D(bounds_.w * -dir, 0.0f)); } else { - tabTweens_[currentTab_]->Reset(Point2D(0.0f, 0.0f)); - - if (orient == ORIENT_HORIZONTAL) { - tabTweens_[tab]->Reset(Point2D(bounds_.w * dir, 0.0f)); - tabTweens_[currentTab_]->Divert(Point2D(bounds_.w * -dir, 0.0f)); - } else { - tabTweens_[tab]->Reset(Point2D(0.0f, bounds_.h * dir)); - tabTweens_[currentTab_]->Divert(Point2D(0.0f, bounds_.h * -dir)); - } - // Actually move it to the initial position now, just to avoid any flicker. - tabTweens_[tab]->Apply(tabs_[tab]); - tabTweens_[tab]->Divert(Point2D(0.0f, 0.0f)); + tabTweens_[tab]->Reset(Point2D(0.0f, bounds_.h * dir)); + tabTweens_[currentTab_]->Divert(Point2D(0.0f, bounds_.h * -dir)); } - tabs_[tab]->SetVisibility(V_VISIBLE); - - currentTab_ = tab; + // Actually move it to the initial position now, just to avoid any flicker. + tabTweens_[tab]->Apply(tabs_[tab]); + tabTweens_[tab]->Divert(Point2D(0.0f, 0.0f)); } - tabStrip_->SetSelection(tab, false); + tabs_[tab]->SetVisibility(V_VISIBLE); + + currentTab_ = tab; return created; } @@ -217,7 +216,7 @@ void TabHolder::OnTabClick(EventParams &e) { // We have e.b set when it was an explicit click action. // In that case, we make the view gone and then visible - this scrolls scrollviews to the top. if (e.b != 0) { - EnsureTab(e.a); + // SetCurrentTab calls EnsureTab if needed. SetCurrentTab((int)e.a); } } From ed15ff7f8e46972a21b358e4abfb1ac1b94adbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 23:31:51 +0200 Subject: [PATCH 5/9] Fix GPUDriverTestScreen --- Common/UI/TabHolder.cpp | 5 +++-- UI/GPUDriverTestScreen.cpp | 7 +++++-- UI/GameBrowser.cpp | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index dd3900d0c0..2e39b588e9 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -145,8 +145,9 @@ bool TabHolder::SetCurrentTab(int tab, bool skipTween) { return false; } - if (currentTab_ < 0 || currentTab_ >= (int)tabs_.size() || !tabs_[currentTab_]) { - _dbg_assert_(false); + if (currentTab_ < 0 || currentTab_ >= (int)tabs_.size()) { + EnsureTab(tab); + _dbg_assert_(tabs_[tab]); // No current tab, so just switch immediately. currentTab_ = tab; tabStrip_->SetSelection(tab, false); diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index 04f968d66f..9670dc2e5a 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -303,8 +303,11 @@ void GPUDriverTestScreen::CreateViews() { tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, TabHolderFlags::Default, nullptr, nullptr, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, Centering::None)); anchor->Add(tabHolder_); - tabHolder_->AddTab("Discard", ImageID::invalid(), new LinearLayout(ORIENT_VERTICAL)); - tabHolder_->AddTab("Shader", ImageID::invalid(), new LinearLayout(ORIENT_VERTICAL)); + tabHolder_->AddTabDeferred("Discard", ImageID::invalid(), []() { return new LinearLayout(ORIENT_VERTICAL); }); + tabHolder_->AddTabDeferred("Shader", ImageID::invalid(), []() { return new LinearLayout(ORIENT_VERTICAL); }); + + tabHolder_->SetInitialTab(0); + tabHolder_->EnsureTab(0); Choice *back = new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK"), new AnchorLayoutParams(190, WRAP_CONTENT, 10, NONE, NONE, 10)); back->OnClick.Handle(this, &UIScreen::OnBack); diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index 8de3362e11..69f3fd9986 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -713,6 +713,7 @@ static bool IsValidPBP(const Path &path, bool allowHomebrew) { return false; std::unique_ptr loader(ConstructFileLoader(path)); + PBPReader pbp(loader.get()); std::vector sfoData; if (!pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) @@ -861,7 +862,7 @@ void GameBrowser::Refresh() { for (size_t i = 0; i < fileInfo.size(); i++) { bool isGame = !fileInfo[i].isDirectory; bool isSaveData = false; - // Check if eboot directory + // Check if eboot directory. TODO: Should ideally be done off-thread somehow. if (!isGame && path_.GetPath().size() >= 4 && IsValidPBP(path_.GetPath() / fileInfo[i].name / "EBOOT.PBP", true)) isGame = true; else if (!isGame && File::Exists(path_.GetPath() / fileInfo[i].name / "PSP_GAME/SYSDIR")) From 51a420be4b569e9a6d90dd14b07d2e4d6153e54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 22:25:30 +0200 Subject: [PATCH 6/9] Android Studio: Bump the plugin version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2ff6736125..bdcc226c99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ plugins { - id("com.android.application") version "9.2.0" apply false + id("com.android.application") version "9.2.1" apply false id("com.google.protobuf") version "0.10.0" apply false } From 7600ec42dd441ae7be2f70aa7403c21f58f016a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 22:54:23 +0200 Subject: [PATCH 7/9] PathBrowser: Rework synchronization. Remove unused enum value in LaunchUrlType. --- Common/File/PathBrowser.cpp | 50 +++++++++++++++++++++---------------- Common/File/PathBrowser.h | 15 ++++++----- Common/System/System.h | 1 - 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index 008e0f5394..2928705514 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -123,6 +123,7 @@ PathBrowser::~PathBrowser() { } void PathBrowser::SetPath(const Path &path) { + std::lock_guard guard(pendingLock_); path_ = path; ApplyRestriction(); HandlePath(); @@ -130,18 +131,20 @@ void PathBrowser::SetPath(const Path &path) { void PathBrowser::RestrictToRoot(const Path &root) { VERBOSE_LOG(Log::IO, "Restricting to root: %s", root.c_str()); + std::lock_guard guard(pendingLock_); restrictedRoot_ = root; } void PathBrowser::HandlePath() { if (!path_.empty() && path_.ToString()[0] == '!') { - if (pendingActive_) - ResetPending(); + if (pendingActive_) { + pendingCancel_ = true; + pendingPath_.clear(); + } ready_ = true; return; } - std::lock_guard guard(pendingLock_); ready_ = false; pendingActive_ = true; pendingCancel_ = false; @@ -169,21 +172,24 @@ void PathBrowser::HandlePath() { } lastPath = pendingPath_; if (lastPath.Type() == PathType::HTTP) { + std::string userAgentCopy = userAgent_; guard.unlock(); results.clear(); - success_ = LoadRemoteFileList(lastPath, userAgent_, &pendingCancel_, results); + bool tempSuccess = LoadRemoteFileList(lastPath, userAgentCopy, &pendingCancel_, results); guard.lock(); + success_ = tempSuccess; } else if (lastPath.empty()) { results.clear(); success_ = true; } else { guard.unlock(); results.clear(); - success_ = File::GetFilesInDir(lastPath, &results, nullptr); + bool tempSuccess = File::GetFilesInDir(lastPath, &results, nullptr); + guard.lock(); + success_ = tempSuccess; if (!success_) { WARN_LOG(Log::IO, "PathBrowser: Failed to list directory: %s", lastPath.c_str()); } - guard.lock(); } if (pendingPath_ == lastPath) { @@ -198,15 +204,9 @@ void PathBrowser::HandlePath() { }); } -void PathBrowser::ResetPending() { - std::lock_guard guard(pendingLock_); - pendingCancel_ = true; - pendingPath_.clear(); -} - bool PathBrowser::GetListing(std::vector &fileInfo, const char *extensionFilter, bool *cancel) { std::unique_lock guard(pendingLock_); - while (!IsListingReady() && (!cancel || !*cancel)) { + while (!ready_ && (!cancel || !*cancel)) { // In case cancel changes, just sleep. TODO: Replace with condition variable. guard.unlock(); sleep_ms(50, "pathbrowser-poll"); @@ -224,7 +224,8 @@ void PathBrowser::ApplyRestriction() { } } -bool PathBrowser::CanNavigateUp() { +bool PathBrowser::CanNavigateUp() const { + std::unique_lock guard(pendingLock_); if (path_ == restrictedRoot_) { return false; } @@ -233,21 +234,26 @@ bool PathBrowser::CanNavigateUp() { void PathBrowser::NavigateUp() { _dbg_assert_(CanNavigateUp()); + + std::unique_lock guard(pendingLock_); path_ = path_.NavigateUp(); ApplyRestriction(); } // TODO: Support paths like "../../hello" void PathBrowser::Navigate(std::string_view path) { - if (path == ".") + std::unique_lock guard(pendingLock_); + if (path == ".") { + // Same directory, nothing to do. return; - if (path == "..") { - NavigateUp(); - } else { - if (path.size() >= 2 && path[1] == ':' && path_.IsRoot()) - path_ = Path(path); - else - path_ = path_ / path; } + if (path == "..") { + path_ = path_.NavigateUp(); + } else if (path.size() >= 2 && path[1] == ':' && path_.IsRoot()) { + path_ = Path(path); + } else { + path_ = path_ / path; + } + ApplyRestriction(); HandlePath(); } diff --git a/Common/File/PathBrowser.h b/Common/File/PathBrowser.h index 8372f25a70..0fdee76888 100644 --- a/Common/File/PathBrowser.h +++ b/Common/File/PathBrowser.h @@ -11,9 +11,9 @@ #include "Common/File/DirListing.h" #include "Common/File/Path.h" -// Abstraction above path that lets you navigate easily. +// Abstraction above path that lets you navigate easily and get listings off-thread. // "/" is a special path that means the root of the file system. On Windows, -// listing this will yield drives. +// listing "/" will yield drives. class PathBrowser { public: PathBrowser() {} @@ -24,11 +24,14 @@ public: HandlePath(); } bool IsListingReady() const { + std::lock_guard guard(pendingLock_); return ready_; } + + // If called before IsListingReady() returns true, will block (becomes synchronous). Don't do that. bool GetListing(std::vector &fileInfo, const char *filter = nullptr, bool *cancel = nullptr); - bool CanNavigateUp(); + bool CanNavigateUp() const; void NavigateUp(); void Navigate(std::string_view subdir); @@ -38,6 +41,7 @@ public: } void SetUserAgent(std::string_view s) { + std::lock_guard guard(pendingLock_); userAgent_ = s; } void RestrictToRoot(const Path &root); @@ -45,12 +49,12 @@ public: return path_.empty(); } bool Success() const { + std::lock_guard guard(pendingLock_); return success_; } private: void HandlePath(); - void ResetPending(); void ApplyRestriction(); Path path_; @@ -59,7 +63,7 @@ private: std::string userAgent_; std::vector pendingFiles_; std::condition_variable pendingCond_; - std::mutex pendingLock_; + mutable std::mutex pendingLock_; std::thread pendingThread_; bool pendingActive_ = false; bool pendingCancel_ = false; @@ -67,4 +71,3 @@ private: bool ready_ = false; bool success_ = true; }; - diff --git a/Common/System/System.h b/Common/System/System.h index 38d9c2a0e3..57c118dc8c 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -49,7 +49,6 @@ enum class LaunchUrlType { EMAIL_ADDRESS, LOCAL_FILE, LOCAL_FOLDER, // Shows the folder. Not supported on all systems of course. - AUTO, }; void System_Vibrate(int length_ms); From 3de68653806bba1be2c3e3b73972bd4db3e63893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 23:11:34 +0200 Subject: [PATCH 8/9] Remove error prone error reporting in MemStickScreen --- UI/MemStickScreen.cpp | 10 +++------- UI/MemStickScreen.h | 2 -- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 69b9d3d68e..28a89a5b88 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -220,8 +220,6 @@ void MemStickScreen::CreateViews() { leftColumn->Add(new RadioButton(&choice_, CHOICE_SET_MANUAL, ms->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); // TODO: Show current folder here if we have one set. } - errorNoticeView_ = leftColumn->Add(new NoticeView(NoticeLevel::WARN, ms->T("Cancelled - try again"), "")); - errorNoticeView_->SetVisibility(UI::V_GONE); if (choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) { UI::View *extraView = nullptr; @@ -419,7 +417,7 @@ void MemStickScreen::UseStorageRoot(UI::EventParams ¶ms) { void MemStickScreen::Browse(UI::EventParams ¶ms) { auto mm = GetI18NCategory(I18NCat::MAINMENU); - System_BrowseForFolder(GetRequesterToken(), mm->T("Choose folder"), g_Config.memStickDirectory, [=](const std::string &value, int) { + System_BrowseForFolder(GetRequesterToken(), mm->T("Choose folder"), g_Config.memStickDirectory, [this](const std::string &value, int) { Path pendingMemStickFolder = Path(value); INFO_LOG(Log::System, "Got folder: '%s' (old: %s)", pendingMemStickFolder.c_str(), g_Config.memStickDirectory.c_str()); // Browse finished. Let's pop up the confirmation dialog. @@ -430,11 +428,9 @@ void MemStickScreen::Browse(UI::EventParams ¶ms) { done_ = true; return; } - errorNoticeView_->SetVisibility(UI::V_GONE); - screenManager()->push(new ConfirmMemstickMoveScreen(pendingMemStickFolder, initialSetup_)); - }, [=]() { - errorNoticeView_->SetVisibility(UI::V_VISIBLE); + }, [this]() { + WARN_LOG(Log::System, "Folder browse cancelled"); }); } diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index b5d4ce4f3c..f27cc97959 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -80,8 +80,6 @@ private: void OnConfirmClick(UI::EventParams ¶ms); void OnChoiceClick(UI::EventParams ¶ms); - NoticeView *errorNoticeView_ = nullptr; - bool initialSetup_; bool storageBrowserWorking_; bool done_ = false; From 2c0f562ff5b7d635f76308931c3dd01bdeadebca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 10 May 2026 23:40:46 +0200 Subject: [PATCH 9/9] ScrollView: Don't have drags of the "opposite" orientation cancel clicks. --- Common/Input/GestureDetector.cpp | 4 ++-- Common/Input/GestureDetector.h | 4 ++++ Common/UI/ScrollView.cpp | 9 +++++++++ Common/UI/ScrollView.h | 3 +-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Common/Input/GestureDetector.cpp b/Common/Input/GestureDetector.cpp index 0e2643a726..bf33013c21 100644 --- a/Common/Input/GestureDetector.cpp +++ b/Common/Input/GestureDetector.cpp @@ -48,7 +48,7 @@ TouchInput GestureDetector::Update(const TouchInput &touch, const Bounds &bounds const float dragThreshold = 5.0f / g_display.dpi_scale_y; - if (p.distanceY > p.distanceX) { + if ((gestureMask_ & GESTURE_DRAG_VERTICAL) && p.distanceY > p.distanceX) { if (p.down) { double timeDown = time_now_d() - p.downTime; if (!p.active && p.distanceY > dragThreshold) { @@ -63,7 +63,7 @@ TouchInput GestureDetector::Update(const TouchInput &touch, const Bounds &bounds } } - if (p.distanceX > p.distanceY) { + if ((gestureMask_ & GESTURE_DRAG_HORIZONTAL) && p.distanceX > p.distanceY) { if (p.down) { double timeDown = time_now_d() - p.downTime; if (!p.active && p.distanceX > dragThreshold) { diff --git a/Common/Input/GestureDetector.h b/Common/Input/GestureDetector.h index 3e5754a7ab..ac072fbdd9 100644 --- a/Common/Input/GestureDetector.h +++ b/Common/Input/GestureDetector.h @@ -13,6 +13,7 @@ enum Gesture { GESTURE_DRAG_HORIZONTAL = 2, GESTURE_TWO_FINGER_ZOOM = 4, GESTURE_TWO_FINGER_ZOOM_ROTATE = 8, + GESTURE_ALL = 15, }; // May track multiple gestures at the same time. You simply call GetGestureInfo @@ -24,6 +25,7 @@ public: void UpdateFrame(); bool IsGestureActive(Gesture gesture, int touchId) const; bool GetGestureInfo(Gesture gesture, int touchId, float info[4]) const; + void SetGestureMask(Gesture gestureMask) { gestureMask_ = gestureMask; } private: enum Locals { @@ -48,4 +50,6 @@ private: }; Pointer pointers[MAX_PTRS]{}; + + Gesture gestureMask_ = GESTURE_ALL; }; diff --git a/Common/UI/ScrollView.cpp b/Common/UI/ScrollView.cpp index 448b37e6a0..e426a792b7 100644 --- a/Common/UI/ScrollView.cpp +++ b/Common/UI/ScrollView.cpp @@ -12,6 +12,15 @@ namespace UI { float ScrollView::lastScrollPosX = 0; float ScrollView::lastScrollPosY = 0; +ScrollView::ScrollView(Orientation orientation, LayoutParams *layoutParams) + : ViewGroup(layoutParams), orientation_(orientation) { + if (orientation == ORIENT_HORIZONTAL) { + gesture_.SetGestureMask(GESTURE_DRAG_HORIZONTAL); + } else { + gesture_.SetGestureMask(GESTURE_DRAG_VERTICAL); + } +} + ScrollView::~ScrollView() { lastScrollPosX = 0; lastScrollPosY = 0; diff --git a/Common/UI/ScrollView.h b/Common/UI/ScrollView.h index 0e47e28d73..73036ddb92 100644 --- a/Common/UI/ScrollView.h +++ b/Common/UI/ScrollView.h @@ -8,8 +8,7 @@ namespace UI { // A scrollview usually contains just a single child - a linear layout or similar. class ScrollView : public ViewGroup { public: - ScrollView(Orientation orientation, LayoutParams *layoutParams = 0) - : ViewGroup(layoutParams), orientation_(orientation) {} + ScrollView(Orientation orientation, LayoutParams *layoutParams = nullptr); ~ScrollView(); void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override;