From 88fe211f1eeeb555c1e943f6152269a0d952bafa Mon Sep 17 00:00:00 2001 From: GenesiaW <74142392+GenesiaW@users.noreply.github.com> Date: Mon, 19 Jan 2026 01:50:17 +0800 Subject: [PATCH 1/4] feat: Add menu to epub reader --- src/activities/reader/EpubReaderActivity.cpp | 8 +- .../reader/EpubReaderMenuActivity.cpp | 157 ++++++++++++++++++ .../reader/EpubReaderMenuActivity.h | 49 ++++++ 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 src/activities/reader/EpubReaderMenuActivity.cpp create mode 100644 src/activities/reader/EpubReaderMenuActivity.h diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 2eeba80..386a5e9 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -7,7 +7,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" -#include "EpubReaderChapterSelectionActivity.h" +#include "EpubReaderMenuActivity.h" #include "MappedInputManager.h" #include "ScreenComponents.h" #include "fontIds.h" @@ -119,12 +119,16 @@ void EpubReaderActivity::loop() { // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); - enterNewActivity(new EpubReaderChapterSelectionActivity( + enterNewActivity(new EpubReaderMenuActivity( this->renderer, this->mappedInput, epub, currentSpineIndex, [this] { exitActivity(); updateRequired = true; }, + [this] { + nextPageNumber = 0; + section.reset(); + }, [this](const int newSpineIndex) { if (currentSpineIndex != newSpineIndex) { currentSpineIndex = newSpineIndex; diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp new file mode 100644 index 0000000..e24574f --- /dev/null +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -0,0 +1,157 @@ +#include "EpubReaderMenuActivity.h" + +#include + +#include "CrossPointSettings.h" +#include "MappedInputManager.h" +#include "activities/reader/EpubReaderChapterSelectionActivity.h" +#include "activities/settings/SettingsActivity.h" +#include "fontIds.h" + +namespace { +constexpr int MENU_ITEMS_COUNT = 2; +const char* menuItems[MENU_ITEMS_COUNT] = {"Chapters", "Settings"}; +} // namespace + +void EpubReaderMenuActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void EpubReaderMenuActivity::onEnter() { + ActivityWithSubactivity::onEnter(); + + if (!epub) { + return; + } + + renderingMutex = xSemaphoreCreateMutex(); + + // Trigger first update + updateRequired = true; + xTaskCreate(&EpubReaderMenuActivity::taskTrampoline, "EpubReaderMenuActivityTask", + 2048, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void EpubReaderMenuActivity::onExit() { + ActivityWithSubactivity::onExit(); + + // Wait until not rendering to delete task to avoid killing mid-instruction to EPD + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void EpubReaderMenuActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) || + mappedInput.wasReleased(MappedInputManager::Button::Left); + const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) || + mappedInput.wasReleased(MappedInputManager::Button::Right); + + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + if (selectedItemIndex == 0) { + onSelectChapters(); + } else if (selectedItemIndex == 1) { + onSelectSettings(); + } + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + onGoBack(); + } else if (prevReleased) { + selectedItemIndex = (selectedItemIndex + MENU_ITEMS_COUNT - 1) % MENU_ITEMS_COUNT; + updateRequired = true; + } else if (nextReleased) { + selectedItemIndex = (selectedItemIndex + 1) % MENU_ITEMS_COUNT; + updateRequired = true; + } +} + +void EpubReaderMenuActivity::onSelectChapters() { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new EpubReaderChapterSelectionActivity( + this->renderer, this->mappedInput, epub, currentSpineIndex, + [this] { + exitActivity(); + updateRequired = true; + }, + [this](const int newSpineIndex) { + exitActivity(); + updateRequired = true; + onSelectSpineIndex(newSpineIndex); + })); + xSemaphoreGive(renderingMutex); +} + +void EpubReaderMenuActivity::onSelectSettings() { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + // sets orientation to portrait when going into settings + renderer.setOrientation(GfxRenderer::Orientation::Portrait); + enterNewActivity(new SettingsActivity(this->renderer, this->mappedInput, [this] { + // resets orientation based on settings + switch (SETTINGS.orientation) { + case CrossPointSettings::ORIENTATION::PORTRAIT: + renderer.setOrientation(GfxRenderer::Orientation::Portrait); + break; + case CrossPointSettings::ORIENTATION::LANDSCAPE_CW: + renderer.setOrientation(GfxRenderer::Orientation::LandscapeClockwise); + break; + case CrossPointSettings::ORIENTATION::INVERTED: + renderer.setOrientation(GfxRenderer::Orientation::PortraitInverted); + break; + case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW: + renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise); + break; + default: + break; + } + exitActivity(); + resetSectionHelper(); + })); + xSemaphoreGive(renderingMutex); +} + +void EpubReaderMenuActivity::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + renderScreen(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void EpubReaderMenuActivity::renderScreen() { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + + const std::string title = + renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD); + + renderer.fillRect(0, 60 + (selectedItemIndex % MENU_ITEMS_COUNT) * 30 - 2, pageWidth - 1, 30); + for (int i = 0; i < MENU_ITEMS_COUNT; i++) { + renderer.drawText(UI_10_FONT_ID, 35, 60 + (i % MENU_ITEMS_COUNT) * 30, menuItems[i], i != selectedItemIndex); + } + + const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h new file mode 100644 index 0000000..d8c1003 --- /dev/null +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include +#include + +#include + +#include "../ActivityWithSubActivity.h" + +class EpubReaderMenuActivity final : public ActivityWithSubactivity { + std::shared_ptr epub; + std::unique_ptr
section = nullptr; + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + int currentSpineIndex = 0; + int selectedItemIndex = 0; + int nextPageNumber = 0; + bool updateRequired = false; + const std::function onGoBack; + + //Reset to first section to prevent out of bound issue when render setting changes + const std::function resetSectionHelper; + const std::function onSelectSpineIndex; + + void onSelectChapters(); + void onSelectSettings(); + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void renderScreen(); + + public: + explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, + const std::shared_ptr& epub, const int currentSpineIndex, + const std::function& onGoBack, + const std::function& resetSectionHelper, + const std::function& onSelectSpineIndex) + : ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput), + epub(epub), + currentSpineIndex(currentSpineIndex), + onGoBack(onGoBack), + resetSectionHelper(resetSectionHelper), + onSelectSpineIndex(onSelectSpineIndex) {} + void onEnter() override; + void onExit() override; + void loop() override; +}; From ef6ef0fec2590c6d73e5243eb35430c270b49054 Mon Sep 17 00:00:00 2001 From: GenesiaW <74142392+GenesiaW@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:19:43 +0800 Subject: [PATCH 2/4] fix formatting --- src/activities/reader/EpubReaderMenuActivity.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index d8c1003..5c9bd9b 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -7,7 +7,7 @@ #include -#include "../ActivityWithSubActivity.h" +#include "activities/ActivityWithSubactivity.h" class EpubReaderMenuActivity final : public ActivityWithSubactivity { std::shared_ptr epub; From 1300664c879336900c28638302d9e4c01737d6d9 Mon Sep 17 00:00:00 2001 From: GenesiaW <74142392+GenesiaW@users.noreply.github.com> Date: Mon, 19 Jan 2026 02:32:35 +0800 Subject: [PATCH 3/4] fix formatting --- src/activities/reader/EpubReaderMenuActivity.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 5c9bd9b..f200836 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -19,8 +19,8 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { int nextPageNumber = 0; bool updateRequired = false; const std::function onGoBack; - - //Reset to first section to prevent out of bound issue when render setting changes + + // Reset to first section to prevent out of bound issue when render setting changes const std::function resetSectionHelper; const std::function onSelectSpineIndex; From 11b6a501d41295d55dc58fed5c72765967a62789 Mon Sep 17 00:00:00 2001 From: GenesiaW <74142392+GenesiaW@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:33:01 +0800 Subject: [PATCH 4/4] feat: revert to single press to chapters and long press to menu --- src/activities/reader/EpubReaderActivity.cpp | 29 +++++++++++++++++-- .../reader/EpubReaderMenuActivity.h | 1 - 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 386a5e9..ef38225 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -7,6 +7,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" +#include "EpubReaderChapterSelectionActivity.h" #include "EpubReaderMenuActivity.h" #include "MappedInputManager.h" #include "ScreenComponents.h" @@ -114,8 +115,8 @@ void EpubReaderActivity::loop() { return; } - // Enter chapter selection activity - if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + // Enter shortcut menu activity + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= skipChapterMs) { // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); exitActivity(); @@ -139,6 +140,30 @@ void EpubReaderActivity::loop() { updateRequired = true; })); xSemaphoreGive(renderingMutex); + return; + } + + // Enter chapter selection activity + if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new EpubReaderChapterSelectionActivity( + this->renderer, this->mappedInput, epub, currentSpineIndex, + [this] { + exitActivity(); + updateRequired = true; + }, + [this](const int newSpineIndex) { + if (currentSpineIndex != newSpineIndex) { + currentSpineIndex = newSpineIndex; + nextPageNumber = 0; + section.reset(); + } + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + return; } // Long press BACK (1s+) goes directly to home diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index f200836..6b2c6cb 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -16,7 +16,6 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity { SemaphoreHandle_t renderingMutex = nullptr; int currentSpineIndex = 0; int selectedItemIndex = 0; - int nextPageNumber = 0; bool updateRequired = false; const std::function onGoBack;