diff options
Diffstat (limited to 'source/Core/Threads/OperatingModes/SettingsMenu.cpp')
-rw-r--r-- | source/Core/Threads/OperatingModes/SettingsMenu.cpp | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/source/Core/Threads/OperatingModes/SettingsMenu.cpp b/source/Core/Threads/OperatingModes/SettingsMenu.cpp new file mode 100644 index 00000000..b1f6e00e --- /dev/null +++ b/source/Core/Threads/OperatingModes/SettingsMenu.cpp @@ -0,0 +1,275 @@ +#include "OperatingModes.h" +#include "ScrollMessage.hpp" + +#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 + */ + +/** + * 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<uint8_t>(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); + } + item->draw(); + } else { + + uint16_t *isRenderingHelp = &(cxt->scratch_state.state6); + *isRenderingHelp = 1; + // Draw description + const char *description = translatedString(Tr->SettingsDescriptions[item->description - 1]); + drawScrollingText(description, (xTaskGetTickCount() - lastButtonTime) - HELP_TEXT_TIMEOUT_TICKS); + } +} + +uint16_t getMenuLength(const menuitem *menu, const uint16_t stop) { + // walk this menu to find the length + uint16_t counter = 0; + for (uint16_t pos = 0; pos < stop; pos++) { + // End of list + if (menu[pos].draw == nullptr) { + return counter; + } + // Otherwise increment for each visible item (null == always, or if not check function) + if (menu[pos].isVisible == nullptr || menu[pos].isVisible()) { + counter++; + } + } + return counter; +} + +OperatingMode moveToNextEntry(guiContext *cxt) { + uint16_t *mainEntry = &(cxt->scratch_state.state1); + uint16_t *subEntry = &(cxt->scratch_state.state2); + uint16_t *currentMenuLength = &(cxt->scratch_state.state5); + uint16_t *isRenderingHelp = &(cxt->scratch_state.state6); + + if (*isRenderingHelp) { + *isRenderingHelp = 0; + } else { + *currentMenuLength = 0; // Reset menu length + // Scroll down + // We can increment freely _once_ + cxt->transitionMode = TransitionAnimation::Down; + if (*subEntry == 0) { + (*mainEntry) += 1; + + if (rootSettingsMenu[*mainEntry].draw == nullptr) { + // We are off the end of the menu now + saveSettings(); + cxt->transitionMode = TransitionAnimation::Left; + return OperatingMode::HomeScreen; + } + // Check if visible + if (rootSettingsMenu[*mainEntry].isVisible != nullptr && !rootSettingsMenu[*mainEntry].isVisible()) { + // We need to move on as this one isn't visible + return moveToNextEntry(cxt); + } + } else { + (*subEntry) += 1; + + // If the new entry is null, we need to exit + if (subSettingsMenus[*mainEntry][(*subEntry) - 1].draw == nullptr) { + (*subEntry) = 0; // Reset back to the main menu + cxt->transitionMode = TransitionAnimation::Left; + // Have to break early to avoid the below check underflowing + return OperatingMode::SettingsMenu; + } + // Check if visible + if (subSettingsMenus[*mainEntry][(*subEntry) - 1].isVisible != nullptr && !subSettingsMenus[*mainEntry][(*subEntry) - 1].isVisible()) { + // We need to move on as this one isn't visible + return moveToNextEntry(cxt); + } + } + } + return OperatingMode::SettingsMenu; +} + +OperatingMode gui_SettingsMenu(const ButtonState buttons, guiContext *cxt) { + // Render out the current settings menu + // State 1 -> Root menu + // State 2 -> Sub entry + // Draw main entry if sub-entry is 0, otherwise draw sub-entry + + uint16_t *mainEntry = &(cxt->scratch_state.state1); + uint16_t *subEntry = &(cxt->scratch_state.state2); + uint32_t *autoRepeatAcceleration = &(cxt->scratch_state.state3); + uint32_t *autoRepeatTimer = &(cxt->scratch_state.state4); + uint16_t *currentMenuLength = &(cxt->scratch_state.state5); + uint16_t *isRenderingHelp = &(cxt->scratch_state.state6); + + 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) - 1; + } + 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, 128 /* Max length of any menu*/); + } + + if (*isRenderingHelp == 0) { + // Draw scroll + + // Get virtual pos by counting entries from start to _here_ + uint16_t currentVirtualPosition = getMenuLength(currentMenu, currentScreen + 1); + if (currentVirtualPosition > 0) { + currentVirtualPosition--; + } + // The height of the indicator is screen res height / total menu entries + uint8_t indicatorHeight = OLED_HEIGHT / *currentMenuLength; + + if (indicatorHeight == 0) { + indicatorHeight = 1; // always at least 1 pixel + } + + uint16_t position = (OLED_HEIGHT * (uint16_t)currentVirtualPosition) / *currentMenuLength; + + bool showScrollbar = true; + + // Store if its the last option for this setting + bool isLastOptionForSetting = false; + if ((int)currentMenu[currentScreen].autoSettingOption < (int)SettingsOptions::SettingsOptionsLength) { + isLastOptionForSetting = isLastSettingValue(currentMenu[currentScreen].autoSettingOption); + } + + // Last settings menu entry, reset scroll show back so it flashes + if (isLastOptionForSetting) { + showScrollbar = false; + } + + // Or Flash it + showScrollbar |= (xTaskGetTickCount() % (TICKS_SECOND / 4) < (TICKS_SECOND / 8)); + + if (showScrollbar) { + OLED::drawScrollIndicator((uint8_t)position, indicatorHeight); + } + } + // Now handle user button input + + auto callIncrementHandler = [&]() { + if (currentMenu[currentScreen].incrementHandler != nullptr) { + currentMenu[currentScreen].incrementHandler(); + } else if ((int)currentMenu[currentScreen].autoSettingOption < (int)SettingsOptions::SettingsOptionsLength) { + nextSettingValue(currentMenu[currentScreen].autoSettingOption); + } + return false; + }; + + // + OperatingMode newMode = OperatingMode::SettingsMenu; + switch (buttons) { + case BUTTON_NONE: + (*autoRepeatAcceleration) = 0; // reset acceleration + (*autoRepeatTimer) = 0; // reset acceleration + break; + case BUTTON_BOTH: + if (*subEntry == 0) { + saveSettings(); + cxt->transitionMode = TransitionAnimation::Left; + return OperatingMode::HomeScreen; + } else { + cxt->transitionMode = TransitionAnimation::Left; + *subEntry = 0; + return OperatingMode::SettingsMenu; + } + break; + + case BUTTON_F_LONG: + if (xTaskGetTickCount() + (*autoRepeatAcceleration) > (*autoRepeatTimer) + PRESS_ACCEL_INTERVAL_MAX) { + callIncrementHandler(); + // Update the check for if its the last version + bool isLastOptionForSetting = false; + if ((int)currentMenu[currentScreen].autoSettingOption < (int)SettingsOptions::SettingsOptionsLength) { + isLastOptionForSetting = isLastSettingValue(currentMenu[currentScreen].autoSettingOption); + } + + if (isLastOptionForSetting) { + (*autoRepeatTimer) = TICKS_SECOND * 2; + } else { + (*autoRepeatTimer) = 0; + } + (*autoRepeatTimer) += xTaskGetTickCount(); + (*autoRepeatAcceleration) += PRESS_ACCEL_STEP; + *currentMenuLength = 0; // Reset incase menu visible changes + } + break; + case BUTTON_F_SHORT: + // Increment setting + if (*isRenderingHelp) { + *isRenderingHelp = 0; + } else { + *currentMenuLength = 0; // Reset incase menu visible changes + if (*subEntry == 0) { + // In a root menu, if its null handler we enter the menu + if (currentMenu[currentScreen].incrementHandler != nullptr) { + currentMenu[currentScreen].incrementHandler(); + } else { + (*subEntry) += 1; + cxt->transitionMode = TransitionAnimation::Right; + } + } else { + callIncrementHandler(); + } + } + break; + case BUTTON_B_LONG: + if (xTaskGetTickCount() + (*autoRepeatAcceleration) > (*autoRepeatTimer) + PRESS_ACCEL_INTERVAL_MAX) { + (*autoRepeatTimer) = xTaskGetTickCount(); + (*autoRepeatAcceleration) += PRESS_ACCEL_STEP; + } else { + break; + } + /* Fall through*/ + case BUTTON_B_SHORT: + // Increment menu item + + newMode = moveToNextEntry(cxt); + break; + + default: + break; + } + if ((PRESS_ACCEL_INTERVAL_MAX - (*autoRepeatAcceleration)) < PRESS_ACCEL_INTERVAL_MIN) { + (*autoRepeatAcceleration) = PRESS_ACCEL_INTERVAL_MAX - PRESS_ACCEL_INTERVAL_MIN; + } + + // Otherwise we stay put for next render iteration + return newMode; +}
\ No newline at end of file |