From 783c0c1e9bcab146208ad5b09863eab7bffe3077 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Mon, 13 Apr 2026 10:54:21 +0200 Subject: [PATCH] Fix activity bleeding --- src/activities/ActivityManager.cpp | 29 ++++++++++++++++++++++++++++- src/activities/ActivityManager.h | 6 ++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index b19f5a4f..b17d4542 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -54,7 +54,26 @@ void ActivityManager::renderTaskLoop() { } void ActivityManager::loop() { - if (currentActivity) { + // Drain leftover input after an activity transition so that the button press/release + // used to leave one activity cannot bleed into the next. We consume events until + // every button is released and no press/release edges remain. + if (drainInput) { + if (mappedInput.wasAnyPressed() || mappedInput.wasAnyReleased() || + mappedInput.isPressed(MappedInputManager::Button::Back) || + mappedInput.isPressed(MappedInputManager::Button::Confirm) || + mappedInput.isPressed(MappedInputManager::Button::Left) || + mappedInput.isPressed(MappedInputManager::Button::Right) || + mappedInput.isPressed(MappedInputManager::Button::Up) || + mappedInput.isPressed(MappedInputManager::Button::Down)) { + // Still have pending input — skip the activity loop but continue with + // the rest (pending-action processing, render flushing) so that + // transitions and screen updates are not delayed. + } else { + drainInput = false; + } + } + + if (!drainInput && currentActivity) { // Note: do not hold a lock here, the loop() method must be responsible for acquire one if needed currentActivity->loop(); } @@ -109,6 +128,10 @@ void ActivityManager::loop() { handler(pendingResult); } + // Arm input drain so the button that triggered the pop doesn't bleed into the + // restored activity (or into a new activity the handler just pushed). + drainInput = true; + // Request an update to ensure the popped activity gets re-rendered if (pendingAction == PendingAction::None) { requestUpdate(); @@ -141,6 +164,10 @@ void ActivityManager::loop() { lock.unlock(); // onEnter may acquire its own lock currentActivity->onEnter(); + // Arm input drain so the button that triggered the transition doesn't bleed + // into the new activity. + drainInput = true; + // onEnter may request another pending action, we will handle it in the next loop iteration continue; } diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index c8702959..33de24b2 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -62,6 +62,12 @@ class ActivityManager { // This variable must only be set by the main loop, to avoid race conditions bool requestedUpdate = false; + // When true, input events are consumed (discarded) until all buttons are released + // and no press/release events remain. Armed automatically on activity transitions + // (push / pop / replace) so that the button used to leave one activity cannot bleed + // into the next one. + bool drainInput = false; + public: explicit ActivityManager(GfxRenderer& renderer, MappedInputManager& mappedInput) : renderer(renderer), mappedInput(mappedInput), renderingMutex(xSemaphoreCreateMutex()) {