diff --git a/src/activities/util/QuickMenuActivity.cpp b/src/activities/util/QuickMenuActivity.cpp new file mode 100644 index 0000000..bfaa896 --- /dev/null +++ b/src/activities/util/QuickMenuActivity.cpp @@ -0,0 +1,173 @@ +#include "QuickMenuActivity.h" + +#include + +#include "MappedInputManager.h" +#include "fontIds.h" + +namespace { +constexpr int MENU_ITEM_COUNT = 4; +const char* MENU_ITEMS[MENU_ITEM_COUNT] = {"Dictionary", "Bookmark", "Clear Cache", "Settings"}; +const char* MENU_DESCRIPTIONS_ADD[MENU_ITEM_COUNT] = { + "Look up a word", + "Add bookmark to this page", + "Free up storage space", + "Open settings menu" +}; +const char* MENU_DESCRIPTIONS_REMOVE[MENU_ITEM_COUNT] = { + "Look up a word", + "Remove bookmark from this page", + "Free up storage space", + "Open settings menu" +}; +} // namespace + +void QuickMenuActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void QuickMenuActivity::onEnter() { + Activity::onEnter(); + + renderingMutex = xSemaphoreCreateMutex(); + + // Reset selection + selectedIndex = 0; + + // Trigger first update + updateRequired = true; + + xTaskCreate(&QuickMenuActivity::taskTrampoline, "QuickMenuTask", + 2048, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void QuickMenuActivity::onExit() { + Activity::onExit(); + + // Wait until not rendering to delete task + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void QuickMenuActivity::loop() { + // Handle back button - cancel + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + onCancel(); + return; + } + + // Handle confirm button - select current option + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + QuickMenuAction action; + switch (selectedIndex) { + case 0: + action = QuickMenuAction::DICTIONARY; + break; + case 1: + action = QuickMenuAction::ADD_BOOKMARK; + break; + case 2: + action = QuickMenuAction::CLEAR_CACHE; + break; + case 3: + default: + action = QuickMenuAction::GO_TO_SETTINGS; + break; + } + onActionSelected(action); + return; + } + + // Handle navigation + const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left); + const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right); + + if (prevPressed) { + selectedIndex = (selectedIndex + MENU_ITEM_COUNT - 1) % MENU_ITEM_COUNT; + updateRequired = true; + } else if (nextPressed) { + selectedIndex = (selectedIndex + 1) % MENU_ITEM_COUNT; + updateRequired = true; + } +} + +void QuickMenuActivity::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void QuickMenuActivity::render() const { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + + // Get bezel offsets + const int bezelTop = renderer.getBezelOffsetTop(); + const int bezelLeft = renderer.getBezelOffsetLeft(); + const int bezelRight = renderer.getBezelOffsetRight(); + const int bezelBottom = renderer.getBezelOffsetBottom(); + + // Calculate usable content area + const int marginLeft = 20 + bezelLeft; + const int marginRight = 20 + bezelRight; + const int marginTop = 15 + bezelTop; + const int contentWidth = pageWidth - marginLeft - marginRight; + const int contentHeight = pageHeight - marginTop - 60 - bezelBottom; // 60 for button hints + + // Draw header + renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Quick Menu", true, EpdFontFamily::BOLD); + + // Select descriptions based on bookmark state + const char* const* descriptions = isPageBookmarked ? MENU_DESCRIPTIONS_REMOVE : MENU_DESCRIPTIONS_ADD; + + // Draw menu items centered in content area + constexpr int itemHeight = 50; // Height for each menu item (including description) + const int startY = marginTop + (contentHeight - (MENU_ITEM_COUNT * itemHeight)) / 2; + + for (int i = 0; i < MENU_ITEM_COUNT; i++) { + const int itemY = startY + i * itemHeight; + const bool isSelected = (i == selectedIndex); + + // Draw selection highlight (black fill) for selected item + if (isSelected) { + renderer.fillRect(marginLeft + 10, itemY - 2, contentWidth - 20, itemHeight - 6); + } + + // Draw menu item text + const char* itemText = MENU_ITEMS[i]; + // For bookmark item, show different text based on state + if (i == 1) { + itemText = isPageBookmarked ? "Remove Bookmark" : "Add Bookmark"; + } + + renderer.drawText(UI_10_FONT_ID, marginLeft + 20, itemY, itemText, !isSelected); + renderer.drawText(SMALL_FONT_ID, marginLeft + 20, itemY + 22, descriptions[i], !isSelected); + } + + // Draw help text at bottom + const auto labels = mappedInput.mapLabels("\xc2\xab Back", "Select", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/util/QuickMenuActivity.h b/src/activities/util/QuickMenuActivity.h new file mode 100644 index 0000000..708f80c --- /dev/null +++ b/src/activities/util/QuickMenuActivity.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include + +#include + +#include "../Activity.h" + +// Enum for quick menu selection +enum class QuickMenuAction { DICTIONARY, ADD_BOOKMARK, CLEAR_CACHE, GO_TO_SETTINGS }; + +/** + * QuickMenuActivity presents a quick access menu triggered by short power button press. + * Options: + * - "Dictionary" - Look up a word + * - "Add/Remove Bookmark" - Toggle bookmark on current page + * + * The onActionSelected callback is called with the user's choice. + * The onCancel callback is called if the user presses back. + */ +class QuickMenuActivity final : public Activity { + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + int selectedIndex = 0; + bool updateRequired = false; + const std::function onActionSelected; + const std::function onCancel; + const bool isPageBookmarked; // True if current page already has a bookmark + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render() const; + + public: + explicit QuickMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::function& onActionSelected, + const std::function& onCancel, bool isPageBookmarked = false) + : Activity("QuickMenu", renderer, mappedInput), + onActionSelected(onActionSelected), + onCancel(onCancel), + isPageBookmarked(isPageBookmarked) {} + void onEnter() override; + void onExit() override; + void loop() override; +};