From 969cadc3eb483e28cd3bd41ee5e53b0f4fcf0b13 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sun, 2 May 2021 16:12:41 +0800 Subject: [PATCH] Change description scroll message behaviour (#950) * Refactor: Extract common scroll message code * Change desc. scrolling to stop at the start and end * Extract `ScrollMessage` to its own file --- source/Core/Inc/ScrollMessage.hpp | 53 +++++++++++++++++++ source/Core/Src/ScrollMessage.cpp | 68 ++++++++++++++++++++++++ source/Core/Src/gui.cpp | 88 ++++++------------------------- 3 files changed, 138 insertions(+), 71 deletions(-) create mode 100644 source/Core/Inc/ScrollMessage.hpp create mode 100644 source/Core/Src/ScrollMessage.cpp diff --git a/source/Core/Inc/ScrollMessage.hpp b/source/Core/Inc/ScrollMessage.hpp new file mode 100644 index 00000000..3bce9aaf --- /dev/null +++ b/source/Core/Inc/ScrollMessage.hpp @@ -0,0 +1,53 @@ +#ifndef SCROLL_MESSAGE_HPP_ +#define SCROLL_MESSAGE_HPP_ + +#include + +/** + * A helper class for showing a full-screen scrolling message. + */ +class ScrollMessage { + uint32_t messageStart = 0; + int16_t lastOffset = -1; + + /** + * Calcualte the width in pixels of the message string, in the large + * font and taking into account multi-byte chars. + * + * @param message The null-terminated message string. + */ + static uint16_t messageWidth(const char *message); + +public: + ScrollMessage() {} + + /** + * Resets this `ScrollMessage` instance to its initial state. + */ + void reset() { + messageStart = 0; + lastOffset = -1; + } + + /** + * Gets whether this `ScrollMessage` instance is in its initial state. + */ + bool isReset() const { return messageStart == 0; } + + /** + * Draw and update the scroll message if needed. + * + * This function does not call `OLED::refresh()`. If this function + * returns `true`, the caller shall call `OLED::refresh()` to draw the + * modified framebuffer to the OLED screen. + * + * @param message The null-terminated message string. This must be the + * same string as the previous call, unless this `ScrollMessage` instance + * is in its initial state or `reset()` has been called. + * @param currentTick The current tick as returned by `xTaskGetTickCount()`. + * @return Whether the OLED framebuffer has been modified. + */ + bool drawUpdate(const char *message, uint32_t currentTick); +}; + +#endif /* SCROLL_MESSAGE_HPP_ */ diff --git a/source/Core/Src/ScrollMessage.cpp b/source/Core/Src/ScrollMessage.cpp new file mode 100644 index 00000000..159c83ba --- /dev/null +++ b/source/Core/Src/ScrollMessage.cpp @@ -0,0 +1,68 @@ +#include "ScrollMessage.hpp" + +#include "OLED.hpp" +#include "configuration.h" + +/** + * Counts the number of chars in the string excluding the null terminator. + * This is a custom version of `strlen` which takes into account our custom + * double-byte char encoding. + * @param str The input string. + * @return The length of the string. + */ +static uint16_t str_display_len(const char *const str) { + const uint8_t *next = reinterpret_cast(str); + uint16_t count = 0; + while (next[0]) { + if (next[0] <= 0xF0) { + count++; + next++; + } else { + if (!next[1]) { + break; + } + count++; + next += 2; + } + } + return count; +} + +uint16_t ScrollMessage::messageWidth(const char *message) { return FONT_12_WIDTH * str_display_len(message); } + +bool ScrollMessage::drawUpdate(const char *message, uint32_t currentTick) { + bool lcdRefresh = false; + + if (messageStart == 0) { + messageStart = currentTick; + lcdRefresh = true; + } + int16_t messageOffset; + uint16_t msgWidth = messageWidth(message); + if (msgWidth > OLED_WIDTH) { + messageOffset = ((currentTick - messageStart) / (systemSettings.descriptionScrollSpeed == 1 ? TICKS_100MS / 10 : (TICKS_100MS / 5))); + messageOffset %= msgWidth + OLED_WIDTH; // Roll around at the end + if (messageOffset < OLED_WIDTH) { + // Snap the message to the left edge. + messageOffset = OLED_WIDTH; + } else if (messageOffset > msgWidth) { + // Snap the message to the right edge. + messageOffset = msgWidth; + } + } else { + // Centre the message without scrolling. + messageOffset = (OLED_WIDTH - msgWidth) / 2 + msgWidth; + } + + if (lastOffset != messageOffset) { + OLED::clearScreen(); + + //^ Rolling offset based on time + OLED::setCursor((OLED_WIDTH - messageOffset), 0); + OLED::print(message, FontStyle::LARGE); + lastOffset = messageOffset; + lcdRefresh = true; + } + + return lcdRefresh; +} diff --git a/source/Core/Src/gui.cpp b/source/Core/Src/gui.cpp index b5a3b331..2ee0bc8b 100644 --- a/source/Core/Src/gui.cpp +++ b/source/Core/Src/gui.cpp @@ -7,6 +7,7 @@ #include "gui.hpp" #include "Buttons.hpp" +#include "ScrollMessage.hpp" #include "TipThermoModel.h" #include "Translation.h" #include "cmsis_os.h" @@ -255,52 +256,11 @@ static void printShortDescription(SettingsItemIndex settingsItemIndex, uint16_t OLED::setCursor(cursorCharPosition * FONT_12_WIDTH - 2, 0); } -/** - * Counts the number of chars in the string excluding the null terminator. - * This is a custom version of `strlen` which takes into account our custom - * double-byte char encoding. - * @param str The input string. - * @return The length of the string. - */ -static uint16_t str_display_len(const char *const str) { - const uint8_t *next = reinterpret_cast(str); - uint16_t count = 0; - while (next[0]) { - if (next[0] <= 0xF0) { - count++; - next++; - } else { - if (!next[1]) { - break; - } - count++; - next += 2; - } - } - return count; -} - static int userConfirmation(const char *message) { - uint16_t messageWidth = FONT_12_WIDTH * (str_display_len(message) + 7); - uint32_t messageStart = xTaskGetTickCount(); - - OLED::setCursor(0, 0); - int16_t lastOffset = -1; - bool lcdRefresh = true; + ScrollMessage scrollMessage; for (;;) { - int16_t messageOffset = ((xTaskGetTickCount() - messageStart) / (systemSettings.descriptionScrollSpeed == 1 ? TICKS_100MS / 10 : (TICKS_100MS / 5))); - messageOffset %= messageWidth; // Roll around at the end - - if (lastOffset != messageOffset) { - OLED::clearScreen(); - - //^ Rolling offset based on time - OLED::setCursor((OLED_WIDTH - messageOffset), 0); - OLED::print(message, FontStyle::LARGE); - lastOffset = messageOffset; - lcdRefresh = true; - } + bool lcdRefresh = scrollMessage.drawUpdate(message, xTaskGetTickCount()); ButtonState buttons = getButtonState(); switch (buttons) { @@ -321,7 +281,6 @@ static int userConfirmation(const char *message) { if (lcdRefresh) { OLED::refresh(); osDelay(40); - lcdRefresh = false; } } return 0; @@ -1079,14 +1038,14 @@ void gui_Menu(const menuitem *menu) { TickType_t autoRepeatTimer = 0; TickType_t autoRepeatAcceleration = 0; bool earlyExit = false; - TickType_t descriptionStart = 0; - int16_t lastOffset = -1; bool lcdRefresh = true; ButtonState lastButtonState = BUTTON_NONE; uint8_t scrollContentSize = 0; bool scrollBlink = false; bool lastValue = false; + ScrollMessage scrollMessage; + for (uint8_t i = 0; menu[i].draw != nullptr; i++) { scrollContentSize += 1; } @@ -1124,23 +1083,10 @@ void gui_Menu(const menuitem *menu) { scrollBlink = !scrollBlink; if (!lastValue || !scrollBlink) OLED::drawScrollIndicator(position, indicatorHeight); - lastOffset = -1; } else { // Draw description - if (descriptionStart == 0) - descriptionStart = xTaskGetTickCount(); const char *description = translatedString(Tr->SettingsDescriptions[menu[currentScreen].description - 1]); - // lower the value - higher the speed - int16_t descriptionWidth = FONT_12_WIDTH * (str_display_len(description) + 7); - int16_t descriptionOffset = ((xTaskGetTickCount() - descriptionStart) / (systemSettings.descriptionScrollSpeed == 1 ? (TICKS_100MS / 10) : (TICKS_100MS / 5))); - descriptionOffset %= descriptionWidth; // Roll around at the end - if (lastOffset != descriptionOffset) { - OLED::clearScreen(); - OLED::setCursor((OLED_WIDTH - descriptionOffset), 0); - OLED::print(description, FontStyle::LARGE); - lastOffset = descriptionOffset; - lcdRefresh = true; - } + lcdRefresh |= scrollMessage.drawUpdate(description, xTaskGetTickCount()); } ButtonState buttons = getButtonState(); @@ -1152,26 +1098,26 @@ void gui_Menu(const menuitem *menu) { switch (buttons) { case BUTTON_BOTH: - earlyExit = true; // will make us exit next loop - descriptionStart = 0; + earlyExit = true; // will make us exit next loop + scrollMessage.reset(); break; case BUTTON_F_SHORT: // increment - if (descriptionStart == 0) { + if (scrollMessage.isReset()) { if (menu[currentScreen].incrementHandler != nullptr) { lastValue = menu[currentScreen].incrementHandler(); } else { earlyExit = true; } } else - descriptionStart = 0; + scrollMessage.reset(); break; case BUTTON_B_SHORT: - if (descriptionStart == 0) { + if (scrollMessage.isReset()) { currentScreen++; lastValue = false; } else - descriptionStart = 0; + scrollMessage.reset(); break; case BUTTON_F_LONG: if (xTaskGetTickCount() + autoRepeatAcceleration > autoRepeatTimer + PRESS_ACCEL_INTERVAL_MAX) { @@ -1182,7 +1128,7 @@ void gui_Menu(const menuitem *menu) { autoRepeatTimer += xTaskGetTickCount(); - descriptionStart = 0; + scrollMessage.reset(); autoRepeatAcceleration += PRESS_ACCEL_STEP; } @@ -1190,8 +1136,8 @@ void gui_Menu(const menuitem *menu) { case BUTTON_B_LONG: if (xTaskGetTickCount() - autoRepeatTimer + autoRepeatAcceleration > PRESS_ACCEL_INTERVAL_MAX) { currentScreen++; - autoRepeatTimer = xTaskGetTickCount(); - descriptionStart = 0; + autoRepeatTimer = xTaskGetTickCount(); + scrollMessage.reset(); autoRepeatAcceleration += PRESS_ACCEL_STEP; } @@ -1213,8 +1159,8 @@ void gui_Menu(const menuitem *menu) { if ((xTaskGetTickCount() - lastButtonTime) > (TICKS_SECOND * 30)) { // If user has not pressed any buttons in 30 seconds, exit back a menu layer // This will trickle the user back to the main screen eventually - earlyExit = true; - descriptionStart = 0; + earlyExit = true; + scrollMessage.reset(); } } }