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
This commit is contained in:
cottongin 2026-01-29 12:57:37 -05:00
parent 25e255af50
commit 492cf976f5
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
4 changed files with 224 additions and 31 deletions

View File

@ -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

View File

@ -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

View File

@ -2,16 +2,25 @@
#include <GfxRenderer.h>
#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 outline for item being moved (when cursor is elsewhere)
if (isBeingMoved && !isSelected) {
renderer.drawRect(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) {
// 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[i], !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", "", "");
// 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();
}

View File

@ -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<void()> 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,