From 9e40449c649c6b839ac59979a9970a640fd602ee Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Mon, 3 May 2021 18:23:30 +0800 Subject: [PATCH 1/2] Add menu exit transition animation Part of which was removed in commit 04d72cb. --- source/Core/Src/gui.cpp | 109 +++++++++++++++++++----------- source/Core/Threads/GUIThread.cpp | 15 +++- 2 files changed, 82 insertions(+), 42 deletions(-) diff --git a/source/Core/Src/gui.cpp b/source/Core/Src/gui.cpp index 63eb711f..a24187d3 100644 --- a/source/Core/Src/gui.cpp +++ b/source/Core/Src/gui.cpp @@ -1034,6 +1034,18 @@ static bool settings_enterAdvancedMenu(void) { void gui_Menu(const menuitem *menu) { // Draw the settings menu and provide iteration support etc + + // This is used to detect whether a menu-exit transition should be played. + static bool wasInGuiMenu; + wasInGuiMenu = true; + + enum class NavState { + Idle, + Entering, + ScrollingDown, + Exiting, + }; + uint8_t currentScreen = 0; TickType_t autoRepeatTimer = 0; TickType_t autoRepeatAcceleration = 0; @@ -1043,7 +1055,7 @@ void gui_Menu(const menuitem *menu) { uint8_t scrollContentSize = 0; bool scrollBlink = false; bool lastValue = false; - bool scrollingDown = false; + NavState navState = NavState::Entering; ScrollMessage scrollMessage; @@ -1051,42 +1063,58 @@ void gui_Menu(const menuitem *menu) { scrollContentSize += 1; } - // Animated menu opening. - if (menu[currentScreen].draw != nullptr) { - // This menu is drawn in a secondary framebuffer. - // Then we play a transition from the current primary - // framebuffer to the new buffer. - // The extra buffer is discarded at the end of the transition. - animOpenState = true; - OLED::useSecondaryFramebuffer(true); - OLED::setCursor(0, 0); - OLED::clearScreen(); - menu[currentScreen].draw(); - OLED::useSecondaryFramebuffer(false); - OLED::transitionSecondaryFramebuffer(true); - animOpenState = false; - } - while ((menu[currentScreen].draw != nullptr) && earlyExit == false) { - OLED::setCursor(0, 0); - if (scrollingDown) { + + // Handle menu transition: + if (navState != NavState::Idle) { + // Check if this menu item shall be skipped. If it shall be skipped, + // `draw()` returns true. Draw on the secondary framebuffer as we want + // to keep the primary framebuffer intact for the upcoming transition + // animation. + OLED::useSecondaryFramebuffer(true); + if (menu[currentScreen].draw()) { + currentScreen++; + OLED::useSecondaryFramebuffer(false); + continue; + } + animOpenState = true; + // The menu entering/exiting transition uses the secondary framebuffer, + // but the scroll down transition does not. + if (navState == NavState::ScrollingDown) { + OLED::useSecondaryFramebuffer(false); + } + OLED::setCursor(0, 0); + OLED::clearScreen(); + menu[currentScreen].draw(); + if (navState == NavState::ScrollingDown) { + // Play the scroll down animation. + OLED::maskScrollIndicatorOnOLED(); + OLED::transitionScrollDown(); + } else { + // The menu was drawn in a secondary framebuffer. + // Now we play a transition from the pre-drawn primary + // framebuffer to the new buffer. + // The extra buffer is discarded at the end of the transition. + OLED::useSecondaryFramebuffer(false); + OLED::transitionSecondaryFramebuffer(navState == NavState::Entering); + } + animOpenState = false; + navState = NavState::Idle; } // If the user has hesitated for >=3 seconds, show the long text // Otherwise "draw" the option if ((xTaskGetTickCount() - lastButtonTime < (TICKS_SECOND * 3)) || menu[currentScreen].description == 0) { lcdRefresh = true; + OLED::setCursor(0, 0); OLED::clearScreen(); - if (menu[currentScreen].draw()) { - currentScreen++; - lcdRefresh = false; - } + menu[currentScreen].draw(); uint8_t indicatorHeight = OLED_HEIGHT / scrollContentSize; uint8_t position = OLED_HEIGHT * currentScreen / scrollContentSize; if (lastValue) scrollBlink = !scrollBlink; - if ((!lastValue || !scrollBlink) && !scrollingDown) + if (!lastValue || !scrollBlink) OLED::drawScrollIndicator(position, indicatorHeight); } else { // Draw description @@ -1095,15 +1123,8 @@ void gui_Menu(const menuitem *menu) { } if (lcdRefresh) { - if (scrollingDown) { - OLED::maskScrollIndicatorOnOLED(); - OLED::transitionScrollDown(); - scrollingDown = false; - animOpenState = false; - } else { - OLED::refresh(); // update the LCD - osDelay(40); - } + OLED::refresh(); // update the LCD + osDelay(40); lcdRefresh = false; } @@ -1114,6 +1135,16 @@ void gui_Menu(const menuitem *menu) { lastButtonState = buttons; } + auto callIncrementHandler = [&]() { + wasInGuiMenu = false; + bool res = menu[currentScreen].incrementHandler(); + if (wasInGuiMenu) { + navState = NavState::Exiting; + } + wasInGuiMenu = true; + return res; + }; + switch (buttons) { case BUTTON_BOTH: earlyExit = true; // will make us exit next loop @@ -1123,7 +1154,7 @@ void gui_Menu(const menuitem *menu) { // increment if (scrollMessage.isReset()) { if (menu[currentScreen].incrementHandler != nullptr) { - lastValue = menu[currentScreen].incrementHandler(); + lastValue = callIncrementHandler(); } else { earlyExit = true; } @@ -1133,14 +1164,14 @@ void gui_Menu(const menuitem *menu) { case BUTTON_B_SHORT: if (scrollMessage.isReset()) { currentScreen++; - scrollingDown = true; - lastValue = false; + navState = NavState::ScrollingDown; + lastValue = false; } else scrollMessage.reset(); break; case BUTTON_F_LONG: if (xTaskGetTickCount() + autoRepeatAcceleration > autoRepeatTimer + PRESS_ACCEL_INTERVAL_MAX) { - if ((lastValue = menu[currentScreen].incrementHandler())) + if ((lastValue = callIncrementHandler())) autoRepeatTimer = 1000; else autoRepeatTimer = 0; @@ -1155,7 +1186,7 @@ void gui_Menu(const menuitem *menu) { case BUTTON_B_LONG: if (xTaskGetTickCount() - autoRepeatTimer + autoRepeatAcceleration > PRESS_ACCEL_INTERVAL_MAX) { currentScreen++; - scrollingDown = true; + navState = NavState::ScrollingDown; autoRepeatTimer = xTaskGetTickCount(); scrollMessage.reset(); @@ -1178,8 +1209,6 @@ void gui_Menu(const menuitem *menu) { scrollMessage.reset(); } } - - animOpenState = false; } void enterSettingsMenu() { diff --git a/source/Core/Threads/GUIThread.cpp b/source/Core/Threads/GUIThread.cpp index 8437c4dc..ee9c935a 100644 --- a/source/Core/Threads/GUIThread.cpp +++ b/source/Core/Threads/GUIThread.cpp @@ -754,6 +754,7 @@ void startGUITask(void const *argument __unused) { bool buttonLockout = false; bool tempOnDisplay = false; bool tipDisconnectedDisplay = false; + bool showExitMenuTransition = false; { // Generate the flipped screen into ram for later use // flipped is generated by flipping each row @@ -820,6 +821,10 @@ void startGUITask(void const *argument __unused) { break; case BUTTON_B_SHORT: enterSettingsMenu(); // enter the settings menu + { + OLED::useSecondaryFramebuffer(true); + showExitMenuTransition = true; + } buttonLockout = true; break; default: @@ -921,7 +926,13 @@ void startGUITask(void const *argument __unused) { } } - OLED::refresh(); - GUIDelay(); + if (showExitMenuTransition) { + OLED::useSecondaryFramebuffer(false); + OLED::transitionSecondaryFramebuffer(false); + showExitMenuTransition = false; + } else { + OLED::refresh(); + GUIDelay(); + } } } From d9b0e7cf6b598178af413d9073f50a937a15d485 Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Mon, 3 May 2021 19:01:51 +0800 Subject: [PATCH 2/2] Draw menu icon even during a transition --- source/Core/Src/gui.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/source/Core/Src/gui.cpp b/source/Core/Src/gui.cpp index a24187d3..e56a2ca9 100644 --- a/source/Core/Src/gui.cpp +++ b/source/Core/Src/gui.cpp @@ -951,6 +951,8 @@ static bool settings_setHallEffect(void) { } #endif +// Indicates whether a menu transition is in progress, so that the menu icon +// animation is paused during the transition. static bool animOpenState = false; static void displayMenu(size_t index) { @@ -962,7 +964,6 @@ static void displayMenu(size_t index) { // 2 pixel wide scrolling indicator static TickType_t menuSwitchLoopTick = 0; static size_t menuCurrentIndex = sizeof(rootSettingsMenu) + 1; - static size_t currentFrame = 0; TickType_t step = TICKS_100MS * 5; switch (systemSettings.animationSpeed) { case settingOffSpeed_t::FAST: @@ -974,17 +975,25 @@ static void displayMenu(size_t index) { default: // SLOW or off - defaulted above break; } - if (!animOpenState) { + size_t currentFrame; + if (!animOpenState && systemSettings.animationSpeed != settingOffSpeed_t::OFF) { if (menuCurrentIndex != index) { menuCurrentIndex = index; - currentFrame = systemSettings.animationSpeed == settingOffSpeed_t::OFF ? 2 : 0; menuSwitchLoopTick = xTaskGetTickCount(); } - if (systemSettings.animationSpeed && (systemSettings.animationLoop || currentFrame != 2)) { - currentFrame = ((xTaskGetTickCount() - menuSwitchLoopTick) / step) % 3; + currentFrame = ((xTaskGetTickCount() - menuSwitchLoopTick) / step); + if (systemSettings.animationLoop) { + currentFrame %= 3; + } else if (currentFrame > 2) { + currentFrame = 2; } - OLED::drawArea(OLED_WIDTH - 16 - 2, 0, 16, 16, (&SettingsMenuIcons[index][(16 * 2) * currentFrame])); + } else { + // We want the animation to restart after completing the transition. + menuCurrentIndex = sizeof(rootSettingsMenu) + 1; + // Always draw the last frame if icon animation is disabled. + currentFrame = systemSettings.animationSpeed == settingOffSpeed_t::OFF ? 2 : 0; } + OLED::drawArea(OLED_WIDTH - 16 - 2, 0, 16, 16, (&SettingsMenuIcons[index][(16 * 2) * currentFrame])); } static bool settings_displayCalibrateVIN(void) {