From b38a5396f0195ab44655689a52424208f6e3ed0a Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Wed, 19 Jul 2023 23:13:13 +1000 Subject: [PATCH] More settings rework --- source/Core/Inc/ScrollMessage.hpp | 57 ++------ source/Core/Inc/settingsGUI.hpp | 8 +- source/Core/Src/ScrollMessage.cpp | 31 ++-- source/Core/Src/settingsGUI.cpp | 17 ++- source/Core/Threads/GUIThread.cpp | 4 +- .../Threads/OperatingModes/SettingsMenu.cpp | 138 ++++++++++++++---- 6 files changed, 149 insertions(+), 106 deletions(-) diff --git a/source/Core/Inc/ScrollMessage.hpp b/source/Core/Inc/ScrollMessage.hpp index 3f97fdb9..9352ad4e 100644 --- a/source/Core/Inc/ScrollMessage.hpp +++ b/source/Core/Inc/ScrollMessage.hpp @@ -4,50 +4,21 @@ #include "portmacro.h" #include /** - * A helper class for showing a full-screen scrolling message. + * A helper for showing a full-screen scrolling message. */ -class ScrollMessage { - TickType_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, TickType_t currentTick); -}; +/** + * 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()` offset to 0 at start of scrolling. + */ +void drawScrollingText(const char *message, TickType_t currentTickOffset); #endif /* SCROLL_MESSAGE_HPP_ */ diff --git a/source/Core/Inc/settingsGUI.hpp b/source/Core/Inc/settingsGUI.hpp index 3f89ad0f..07695f41 100644 --- a/source/Core/Inc/settingsGUI.hpp +++ b/source/Core/Inc/settingsGUI.hpp @@ -13,7 +13,6 @@ #include "Settings.h" #include "Translation.h" - #define PRESS_ACCEL_STEP (TICKS_100MS / 3) #define PRESS_ACCEL_INTERVAL_MIN TICKS_100MS #define PRESS_ACCEL_INTERVAL_MAX (TICKS_100MS * 3) @@ -37,8 +36,9 @@ typedef struct { uint8_t shortDescriptionSize; } menuitem; -void enterSettingsMenu(); -bool warnUser(const char *warning, const ButtonState buttons); -extern const menuitem rootSettingsMenu[]; +void enterSettingsMenu(); +bool warnUser(const char *warning, const ButtonState buttons); +extern const menuitem rootSettingsMenu[]; +extern const menuitem *subSettingsMenus[]; #endif /* GUI_HPP_ */ diff --git a/source/Core/Src/ScrollMessage.cpp b/source/Core/Src/ScrollMessage.cpp index d63cede3..a99521a6 100644 --- a/source/Core/Src/ScrollMessage.cpp +++ b/source/Core/Src/ScrollMessage.cpp @@ -28,19 +28,20 @@ static uint16_t str_display_len(const char *const str) { return count; } -uint16_t ScrollMessage::messageWidth(const char *message) { return FONT_12_WIDTH * str_display_len(message); } +/** + * Calculate 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. + */ +uint16_t messageWidth(const char *message) { return FONT_12_WIDTH * str_display_len(message); } -bool ScrollMessage::drawUpdate(const char *message, TickType_t currentTick) { - bool lcdRefresh = false; +void drawScrollingText(const char *message, TickType_t currentTickOffset) { - if (messageStart == 0) { - messageStart = currentTick; - lcdRefresh = true; - } int16_t messageOffset; uint16_t msgWidth = messageWidth(message); if (msgWidth > OLED_WIDTH) { - messageOffset = ((currentTick - messageStart) / (getSettingValue(SettingsOptions::DescriptionScrollSpeed) == 1 ? TICKS_100MS / 10 : (TICKS_100MS / 5))); + messageOffset = (currentTickOffset / (getSettingValue(SettingsOptions::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. @@ -54,15 +55,7 @@ bool ScrollMessage::drawUpdate(const char *message, TickType_t currentTick) { 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; + //^ Rolling offset based on time + OLED::setCursor((OLED_WIDTH - messageOffset), 0); + OLED::print(message, FontStyle::LARGE); } diff --git a/source/Core/Src/settingsGUI.cpp b/source/Core/Src/settingsGUI.cpp index 48281704..5bbd33d9 100644 --- a/source/Core/Src/settingsGUI.cpp +++ b/source/Core/Src/settingsGUI.cpp @@ -440,6 +440,12 @@ const menuitem advancedMenu[] = { /* clang-format on */ +extern const menuitem *subSettingsMenus[] { +#if defined(POW_DC) || defined(POW_QC) || defined(POW_PD) + powerMenu, +#endif + solderingMenu, PowerSavingMenu, UIMenu, advancedMenu, +}; /* ^^^ !!!ENABLE CLANG-FORMAT back!!! ^^^ */ /** @@ -460,10 +466,9 @@ static void printShortDescription(SettingsItemIndex settingsItemIndex, uint16_t } static int userConfirmation(const char *message) { - ScrollMessage scrollMessage; - + TickType_t tickStart = xTaskGetTickCount(); for (;;) { - bool lcdRefresh = scrollMessage.drawUpdate(message, xTaskGetTickCount()); + drawScrollingText(message, xTaskGetTickCount() - tickStart); ButtonState buttons = getButtonState(); switch (buttons) { @@ -481,10 +486,8 @@ static int userConfirmation(const char *message) { return 0; } - if (lcdRefresh) { - OLED::refresh(); - osDelay(40); - } + OLED::refresh(); + osDelay(40); } return 0; } diff --git a/source/Core/Threads/GUIThread.cpp b/source/Core/Threads/GUIThread.cpp index e9d89024..1bffaa60 100644 --- a/source/Core/Threads/GUIThread.cpp +++ b/source/Core/Threads/GUIThread.cpp @@ -18,6 +18,7 @@ extern "C" { #include "Settings.h" #include "TipThermoModel.h" #include "Translation.h" +#include "bflb_platform.h" #include "cmsis_os.h" #include "configuration.h" #include "history.hpp" @@ -63,7 +64,7 @@ void guiRenderLoop(void) { } } } - + MSG("Run GUI %d - %d\r\n", (int)currentOperatingMode, (int)buttons); // Dispatch button state to gui mode OperatingMode newMode = currentOperatingMode; switch (currentOperatingMode) { @@ -144,6 +145,7 @@ void guiRenderLoop(void) { OLED::useSecondaryFramebuffer(false); context.transitionMode = TransitionAnimation::None; // Clear transition flag } + MSG("Post GUI %d - %d\r\n", (int)currentOperatingMode, (int)buttons); // Render done, draw it out OLED::refresh(); } diff --git a/source/Core/Threads/OperatingModes/SettingsMenu.cpp b/source/Core/Threads/OperatingModes/SettingsMenu.cpp index 042587be..410047a1 100644 --- a/source/Core/Threads/OperatingModes/SettingsMenu.cpp +++ b/source/Core/Threads/OperatingModes/SettingsMenu.cpp @@ -1,45 +1,119 @@ #include "OperatingModes.h" +#include "ScrollMessage.hpp" -OperatingMode handleSettingsButtons(const ButtonState buttons, guiContext *cxt) { - switch (buttons) { - case BUTTON_NONE: - // Do nothing - break; - case BUTTON_BOTH: - break; +#define HELP_TEXT_TIMEOUT_TICKS (TICKS_SECOND * 3) +/* + * The settings menu is the most complex bit of GUI code we have + * The menu consists of a two tier menu + * Main menu -> Categories + * Secondary menu -> Settings + * + * For each entry in the menu + */ - case BUTTON_B_LONG: - return OperatingMode::DebugMenuReadout; - break; - case BUTTON_F_LONG: -#ifdef PROFILE_SUPPORT - if (!isTipDisconnected()) { - return OperatingMode::SolderingProfile; +/** + * Prints two small lines (or one line for CJK) of short description for + * setting items and prepares cursor after it. + * @param settingsItemIndex Index of the setting item. + * @param cursorCharPosition Custom cursor char position to set after printing + * description. + */ +static void printShortDescription(SettingsItemIndex settingsItemIndex, uint16_t cursorCharPosition) { + // print short description (default single line, explicit double line) + uint8_t shortDescIndex = static_cast(settingsItemIndex); + OLED::printWholeScreen(translatedString(Tr->SettingsShortNames[shortDescIndex])); + + // prepare cursor for value + // make room for scroll indicator + OLED::setCursor(cursorCharPosition * FONT_12_WIDTH - 2, 0); +} + +// Render a menu, based on the position given +// This will either draw the menu item, or the help text depending on how long its been since button press +void render_menu(const menuitem *item, guiContext *cxt) { + + // If recent interaction or not help text draw the entry + if ((xTaskGetTickCount() - lastButtonTime < HELP_TEXT_TIMEOUT_TICKS) || item->description == 0) { + + if (item->shortDescriptionSize > 0) { + printShortDescription(item->shortDescriptionIndex, item->shortDescriptionSize); } -#else - return OperatingMode::TemperatureAdjust; -#endif - break; - case BUTTON_F_SHORT: - if (!isTipDisconnected()) { - return OperatingMode::Soldering; - } - break; - case BUTTON_B_SHORT: - return OperatingMode::SettingsMenu; - break; - default: - break; + item->draw(); + } else { + // Draw description + const char *description = translatedString(Tr->SettingsDescriptions[item->description - 1]); + drawScrollingText(description, xTaskGetTickCount() - lastButtonTime); } - return OperatingMode::HomeScreen; +} + +uint16_t getMenuLength(const menuitem *menu) { + // walk this menu to find the length + for (uint16_t pos = 0; pos < 64; pos++) { + if (menu[pos].draw == NULL) { + return pos + 1; + } + } + return 0; // Cant find length, be safe } OperatingMode gui_SettingsMenu(const ButtonState buttons, guiContext *cxt) { // Render out the current settings menu // State 1 -> Root menu // State 2 -> Sub entry - uint16_t *mainEntry = &(cxt->scratch_state.state1); - uint16_t *subEntry = &(cxt->scratch_state.state2); + // Draw main entry if sub-entry is 0, otherwise draw sub-entry - return handleSettingsButtons(buttons, cxt); + uint16_t *mainEntry = &(cxt->scratch_state.state1); + uint16_t *subEntry = &(cxt->scratch_state.state2); + uint16_t *currentMenuLength = &(cxt->scratch_state.state5); + + const menuitem *currentMenu; + // Draw the currently on screen item + uint16_t currentScreen; + if (*subEntry == 0) { + // Drawing main menu + currentMenu = rootSettingsMenu; + currentScreen = *mainEntry; + } else { + // Drawing sub menu + currentMenu = subSettingsMenus[*mainEntry]; + currentScreen = *subEntry; + } + render_menu(&(currentMenu[currentScreen]), cxt); + + // Update the cached menu length if unknown + if (*currentMenuLength == 0) { + // We walk the current menu to find the length + *currentMenuLength = getMenuLength(currentMenu); + } + // Draw scroll + uint8_t indicatorHeight = OLED_HEIGHT / *currentMenuLength; + uint8_t position = (OLED_HEIGHT * currentScreen) / *currentMenuLength; + // Draw if not last item + if ((*currentMenuLength != currentScreen) || xTaskGetTickCount() % 1000 < 500) { + OLED::drawScrollIndicator(position, indicatorHeight); + } + + // Now handle user button input + + switch (buttons) { + case BUTTON_NONE: + break; + case BUTTON_BOTH: + return OperatingMode::HomeScreen; + break; + + case BUTTON_B_LONG: + break; + case BUTTON_F_LONG: + + break; + case BUTTON_F_SHORT: + break; + case BUTTON_B_SHORT: + break; + default: + break; + } + // Otherwise we stay put for next render iteration + return OperatingMode::SettingsMenu; } \ No newline at end of file