diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 6ff39c5..41dc534 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -8,6 +8,7 @@ #include "CrossPointSettings.h" #include "CrossPointState.h" #include "EpubReaderChapterSelectionActivity.h" +#include "EpubReaderMenuActivity.h" #include "MappedInputManager.h" #include "RecentBooksStore.h" #include "ScreenComponents.h" @@ -116,9 +117,36 @@ void EpubReaderActivity::loop() { return; } + // 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(); + 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; + nextPageNumber = 0; + section.reset(); + } + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + return; + } + // Enter chapter selection activity if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); const int currentPage = section ? section->currentPage : 0; const int totalPages = section ? section->pageCount : 0; @@ -149,6 +177,7 @@ void EpubReaderActivity::loop() { updateRequired = true; })); xSemaphoreGive(renderingMutex); + return; } // Long press BACK (1s+) goes directly to home 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..6b2c6cb --- /dev/null +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include +#include +#include + +#include + +#include "activities/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; + 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; +};