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:
parent
25e255af50
commit
492cf976f5
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user