From 492cf976f5eee8b2bb25d9d348a85857c465377c Mon Sep 17 00:00:00 2001 From: cottongin Date: Thu, 29 Jan 2026 12:57:37 -0500 Subject: [PATCH] feat(quickmenu): comprehensive quick menu enhancements Quick Menu UI Improvements: - Add navigation button hints (prev/next on front buttons, up/down on side buttons) - Fix orientation-aware margins for button hint areas in landscape modes Screen Rotation Toggle: - Add "Rotate Screen" option to toggle between Portrait and Landscape CCW - Force section reindex when orientation changes to properly reflow content - Position is automatically restored via content offset after reindex Customizable Menu Order: - Add "Edit List Order" option (fixed at bottom of menu) - Pick-and-place reordering: select item to move, navigate to destination, place - Visual feedback: filled highlight for cursor, outlined box for item being moved - Menu order persists in settings (quickMenuOrder array in CrossPointSettings) - New default order: Bookmark, Dictionary, Rotate Screen, Settings, Clear Cache Files changed: - CrossPointSettings.h: Add quickMenuOrder[5] setting - QuickMenuActivity.h/cpp: Edit mode, order rendering, pick-and-place logic - EpubReaderActivity.cpp: Handle TOGGLE_ORIENTATION action with section reset --- src/CrossPointSettings.h | 5 + src/activities/reader/EpubReaderActivity.cpp | 22 ++ src/activities/util/QuickMenuActivity.cpp | 219 ++++++++++++++++--- src/activities/util/QuickMenuActivity.h | 9 +- 4 files changed, 224 insertions(+), 31 deletions(-) diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index e345220..45a20bc 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -155,6 +155,11 @@ class CrossPointSettings { // Pinned list name (empty = none pinned) char pinnedListName[64] = ""; + // Quick menu item order (indices 0-4 representing the 5 menu items) + // Maps to QuickMenuAction enum: 0=Dictionary, 1=Bookmark, 2=ClearCache, 3=Orientation, 4=Settings + // Default order: Bookmark(1), Dictionary(0), Orientation(3), Settings(4), ClearCache(2) + uint8_t quickMenuOrder[5] = {1, 0, 3, 4, 2}; + ~CrossPointSettings() = default; // Get singleton instance diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 97b6cea..41dad88 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -496,6 +496,28 @@ void EpubReaderActivity::loop() { self->onGoToClearCache(); return; } + self->updateRequired = true; + } else if (action == QuickMenuAction::TOGGLE_ORIENTATION) { + // Toggle between Portrait and Landscape CCW + if (SETTINGS.orientation == CrossPointSettings::ORIENTATION::PORTRAIT) { + SETTINGS.orientation = CrossPointSettings::ORIENTATION::LANDSCAPE_CCW; + } else { + SETTINGS.orientation = CrossPointSettings::ORIENTATION::PORTRAIT; + } + SETTINGS.saveToFile(); + + // Apply new orientation to renderer + if (SETTINGS.orientation == CrossPointSettings::ORIENTATION::PORTRAIT) { + self->renderer.setOrientation(GfxRenderer::Orientation::Portrait); + } else { + self->renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise); + } + + // Force section reload with new orientation's viewport dimensions + xSemaphoreTake(cachedMutex, portMAX_DELAY); + self->section.reset(); + xSemaphoreGive(cachedMutex); + self->updateRequired = true; } else if (action == QuickMenuAction::GO_TO_SETTINGS) { // Navigate to Settings activity diff --git a/src/activities/util/QuickMenuActivity.cpp b/src/activities/util/QuickMenuActivity.cpp index 03fb9e6..d7c2c94 100644 --- a/src/activities/util/QuickMenuActivity.cpp +++ b/src/activities/util/QuickMenuActivity.cpp @@ -2,16 +2,25 @@ #include +#include "CrossPointSettings.h" #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"}; +// Base menu item count (reorderable items) +constexpr int BASE_MENU_ITEM_COUNT = 5; +// Total display count including "Edit List Order" +constexpr int DISPLAY_ITEM_COUNT = 6; + +// Menu items indexed by QuickMenuAction enum value +// 0=Dictionary, 1=Bookmark, 2=ClearCache, 3=Orientation, 4=Settings +const char* MENU_ITEMS[BASE_MENU_ITEM_COUNT] = {"Dictionary", "Bookmark", "Clear Cache", "Rotate Screen", "Settings"}; +const char* MENU_DESCRIPTIONS_ADD[BASE_MENU_ITEM_COUNT] = {"Look up a word", "Add bookmark to this page", + "Free up storage space", "Toggle screen orientation", + "Open settings menu"}; +const char* MENU_DESCRIPTIONS_REMOVE[BASE_MENU_ITEM_COUNT] = {"Look up a word", "Remove bookmark from this page", + "Free up storage space", "Toggle screen orientation", + "Open settings menu"}; } // namespace void QuickMenuActivity::taskTrampoline(void* param) { @@ -53,6 +62,16 @@ void QuickMenuActivity::onExit() { } void QuickMenuActivity::loop() { + if (editMode) { + // Edit mode logic + handleEditMode(); + } else { + // Normal mode logic + handleNormalMode(); + } +} + +void QuickMenuActivity::handleNormalMode() { // Handle back button - cancel if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onCancel(); @@ -61,8 +80,22 @@ void QuickMenuActivity::loop() { // Handle confirm button - select current option if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + // Last item is "Edit List Order" + if (selectedIndex == DISPLAY_ITEM_COUNT - 1) { + // Enter edit mode - copy current order to local buffer + for (int i = 0; i < BASE_MENU_ITEM_COUNT; i++) { + localOrder[i] = SETTINGS.quickMenuOrder[i]; + } + editMode = true; + selectedIndex = 0; // Start at first item in edit mode + updateRequired = true; + return; + } + + // Get the action from the order array + const int actionIndex = SETTINGS.quickMenuOrder[selectedIndex]; QuickMenuAction action; - switch (selectedIndex) { + switch (actionIndex) { case 0: action = QuickMenuAction::DICTIONARY; break; @@ -73,6 +106,9 @@ void QuickMenuActivity::loop() { action = QuickMenuAction::CLEAR_CACHE; break; case 3: + action = QuickMenuAction::TOGGLE_ORIENTATION; + break; + case 4: default: action = QuickMenuAction::GO_TO_SETTINGS; break; @@ -88,10 +124,69 @@ void QuickMenuActivity::loop() { mappedInput.wasPressed(MappedInputManager::Button::Right); if (prevPressed) { - selectedIndex = (selectedIndex + MENU_ITEM_COUNT - 1) % MENU_ITEM_COUNT; + selectedIndex = (selectedIndex + DISPLAY_ITEM_COUNT - 1) % DISPLAY_ITEM_COUNT; updateRequired = true; } else if (nextPressed) { - selectedIndex = (selectedIndex + 1) % MENU_ITEM_COUNT; + selectedIndex = (selectedIndex + 1) % DISPLAY_ITEM_COUNT; + updateRequired = true; + } +} + +void QuickMenuActivity::handleEditMode() { + // Handle back button - save and exit edit mode + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + // Save the local order to settings + for (int i = 0; i < BASE_MENU_ITEM_COUNT; i++) { + SETTINGS.quickMenuOrder[i] = localOrder[i]; + } + SETTINGS.saveToFile(); + editMode = false; + movingIndex = -1; + selectedIndex = DISPLAY_ITEM_COUNT - 1; // Select "Edit List Order" when exiting + updateRequired = true; + return; + } + + // Handle confirm button - pick or place item + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (movingIndex < 0) { + // No item selected yet - pick up the current item + movingIndex = selectedIndex; + } else { + // Item is being moved - place it at the current position + if (movingIndex != selectedIndex) { + // Remove item from old position and insert at new position + const uint8_t movingItem = localOrder[movingIndex]; + if (movingIndex < selectedIndex) { + // Moving down - shift items up + for (int i = movingIndex; i < selectedIndex; i++) { + localOrder[i] = localOrder[i + 1]; + } + } else { + // Moving up - shift items down + for (int i = movingIndex; i > selectedIndex; i--) { + localOrder[i] = localOrder[i - 1]; + } + } + localOrder[selectedIndex] = movingItem; + } + movingIndex = -1; // Deselect + } + updateRequired = true; + return; + } + + // Handle navigation - just move cursor + 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 > 0) { + selectedIndex--; + updateRequired = true; + } else if (nextPressed && selectedIndex < BASE_MENU_ITEM_COUNT - 1) { + selectedIndex++; updateRequired = true; } } @@ -120,46 +215,110 @@ void QuickMenuActivity::render() const { 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 + // Button hint space constants + constexpr int FRONT_BUTTON_SPACE = 45; // 40px button height + 5px padding + constexpr int SIDE_BUTTON_SPACE = 50; // 45px button area + 5px padding - // Draw header - renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Quick Menu", true, EpdFontFamily::BOLD); + // Calculate button hint margins based on orientation + // Physical button locations (fixed on device): + // - Front buttons: physical bottom in portrait + // - Side buttons: physical right in portrait + // These map to different logical edges depending on orientation + int frontBtnMarginTop = 0, frontBtnMarginBottom = 0, frontBtnMarginLeft = 0, frontBtnMarginRight = 0; + int sideBtnMarginTop = 0, sideBtnMarginBottom = 0, sideBtnMarginLeft = 0, sideBtnMarginRight = 0; + + switch (renderer.getOrientation()) { + case GfxRenderer::Portrait: + // Front buttons at logical BOTTOM, Side buttons at logical RIGHT + frontBtnMarginBottom = FRONT_BUTTON_SPACE; + sideBtnMarginRight = SIDE_BUTTON_SPACE; + break; + case GfxRenderer::LandscapeClockwise: + // Front buttons at logical LEFT, Side buttons at logical BOTTOM + frontBtnMarginLeft = FRONT_BUTTON_SPACE; + sideBtnMarginBottom = SIDE_BUTTON_SPACE; + break; + case GfxRenderer::PortraitInverted: + // Front buttons at logical TOP, Side buttons at logical LEFT + frontBtnMarginTop = FRONT_BUTTON_SPACE; + sideBtnMarginLeft = SIDE_BUTTON_SPACE; + break; + case GfxRenderer::LandscapeCounterClockwise: + // Front buttons at logical RIGHT, Side buttons at logical TOP + frontBtnMarginRight = FRONT_BUTTON_SPACE; + sideBtnMarginTop = SIDE_BUTTON_SPACE; + break; + } + + // Calculate usable content area with bezel and button hint margins + const int marginLeft = 20 + bezelLeft + frontBtnMarginLeft + sideBtnMarginLeft; + const int marginRight = 20 + bezelRight + frontBtnMarginRight + sideBtnMarginRight; + const int marginTop = 15 + bezelTop + frontBtnMarginTop + sideBtnMarginTop; + const int marginBottom = 15 + bezelBottom + frontBtnMarginBottom + sideBtnMarginBottom; + const int contentWidth = pageWidth - marginLeft - marginRight; + const int contentHeight = pageHeight - marginTop - marginBottom; + + // Draw header - different text in edit mode + const char* headerText = editMode ? "Edit Menu Order" : "Quick Menu"; + renderer.drawCenteredText(UI_12_FONT_ID, marginTop, headerText, true, EpdFontFamily::BOLD); // Select descriptions based on bookmark state const char* const* descriptions = isPageBookmarked ? MENU_DESCRIPTIONS_REMOVE : MENU_DESCRIPTIONS_ADD; + // Get the order array to use (local copy in edit mode, settings otherwise) + const uint8_t* order = editMode ? localOrder : SETTINGS.quickMenuOrder; + // 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; + const int startY = marginTop + (contentHeight - (DISPLAY_ITEM_COUNT * itemHeight)) / 2; - for (int i = 0; i < MENU_ITEM_COUNT; i++) { + for (int i = 0; i < DISPLAY_ITEM_COUNT; i++) { const int itemY = startY + i * itemHeight; const bool isSelected = (i == selectedIndex); + const bool isBeingMoved = (editMode && i == movingIndex); // 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"; + // Draw outline for item being moved (when cursor is elsewhere) + if (isBeingMoved && !isSelected) { + renderer.drawRect(marginLeft + 10, itemY - 2, contentWidth - 20, itemHeight - 6); } - renderer.drawText(UI_10_FONT_ID, marginLeft + 20, itemY, itemText, !isSelected); - renderer.drawText(SMALL_FONT_ID, marginLeft + 20, itemY + 22, descriptions[i], !isSelected); + // Last item is always "Edit List Order" (fixed, not in the order array) + if (i == DISPLAY_ITEM_COUNT - 1) { + renderer.drawText(UI_10_FONT_ID, marginLeft + 20, itemY, "- Edit List Order -", !isSelected); + renderer.drawText(SMALL_FONT_ID, marginLeft + 20, itemY + 22, "Customize menu order", !isSelected); + } else { + // Get the action index from the order array + const int actionIndex = order[i]; + + // Draw menu item text - add indicator for item being moved + const char* itemText = MENU_ITEMS[actionIndex]; + // For bookmark item (action index 1), show different text based on state + if (actionIndex == 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[actionIndex], !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); + // Draw help text at bottom - different hints for edit mode + if (editMode) { + const char* confirmLabel = (movingIndex < 0) ? "Pick" : "Place"; + const auto labels = mappedInput.mapLabels("\xc2\xab Done", confirmLabel, "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + // Side button hints for navigation + renderer.drawSideButtonHints(UI_10_FONT_ID, "<", ">"); + } else { + const auto labels = mappedInput.mapLabels("\xc2\xab Back", "Select", "< Prev", "Next >"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + // Side button hints for up/down navigation + renderer.drawSideButtonHints(UI_10_FONT_ID, "<", ">"); + } renderer.displayBuffer(); } diff --git a/src/activities/util/QuickMenuActivity.h b/src/activities/util/QuickMenuActivity.h index 708f80c..b9fac71 100644 --- a/src/activities/util/QuickMenuActivity.h +++ b/src/activities/util/QuickMenuActivity.h @@ -8,7 +8,7 @@ #include "../Activity.h" // Enum for quick menu selection -enum class QuickMenuAction { DICTIONARY, ADD_BOOKMARK, CLEAR_CACHE, GO_TO_SETTINGS }; +enum class QuickMenuAction { DICTIONARY, ADD_BOOKMARK, CLEAR_CACHE, TOGGLE_ORIENTATION, GO_TO_SETTINGS }; /** * QuickMenuActivity presents a quick access menu triggered by short power button press. @@ -28,9 +28,16 @@ class QuickMenuActivity final : public Activity { const std::function onCancel; const bool isPageBookmarked; // True if current page already has a bookmark + // Edit mode state + bool editMode = false; // True when in edit mode + int movingIndex = -1; // Index of item being moved (-1 if none) + uint8_t localOrder[5] = {0}; // Local copy of order for editing + static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void render() const; + void handleNormalMode(); + void handleEditMode(); public: explicit QuickMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,