diff --git a/src/screens/EpubReaderChapterSelectionScreen.cpp b/src/screens/EpubReaderChapterSelectionScreen.cpp new file mode 100644 index 0000000..26688a4 --- /dev/null +++ b/src/screens/EpubReaderChapterSelectionScreen.cpp @@ -0,0 +1,107 @@ +#include "EpubReaderChapterSelectionScreen.h" + +#include +#include + +#include "config.h" + +constexpr int PAGE_ITEMS = 24; +constexpr int SKIP_PAGE_MS = 700; + +void EpubReaderChapterSelectionScreen::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void EpubReaderChapterSelectionScreen::onEnter() { + if (!epub) { + return; + } + + renderingMutex = xSemaphoreCreateMutex(); + selectorIndex = currentSpineIndex; + + // Trigger first update + updateRequired = true; + xTaskCreate(&EpubReaderChapterSelectionScreen::taskTrampoline, "EpubReaderChapterSelectionScreenTask", + 2048, // Stack size + this, // Parameters + 1, // Priority + &displayTaskHandle // Task handle + ); +} + +void EpubReaderChapterSelectionScreen::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 EpubReaderChapterSelectionScreen::handleInput() { + const bool prevReleased = + inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); + const bool nextReleased = + inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT); + + const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS; + + if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + onSelectSpineIndex(selectorIndex); + } else if (inputManager.wasPressed(InputManager::BTN_BACK)) { + onGoBack(); + } else if (prevReleased) { + if (skipPage) { + selectorIndex = + ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + epub->getSpineItemsCount()) % epub->getSpineItemsCount(); + } else { + selectorIndex = (selectorIndex + epub->getSpineItemsCount() - 1) % epub->getSpineItemsCount(); + } + updateRequired = true; + } else if (nextReleased) { + if (skipPage) { + selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % epub->getSpineItemsCount(); + } else { + selectorIndex = (selectorIndex + 1) % epub->getSpineItemsCount(); + } + updateRequired = true; + } +} + +void EpubReaderChapterSelectionScreen::displayTaskLoop() { + while (true) { + if (updateRequired) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + renderScreen(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void EpubReaderChapterSelectionScreen::renderScreen() { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + renderer.drawCenteredText(READER_FONT_ID, 10, "Select Chapter", true, BOLD); + + const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS; + renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 + 2, pageWidth - 1, 30); + for (int i = pageStartIndex; i < epub->getSpineItemsCount() && i < pageStartIndex + PAGE_ITEMS; i++) { + const int tocIndex = epub->getTocIndexForSpineIndex(i); + if (tocIndex == -1) { + renderer.drawText(UI_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, "Unnamed", i != selectorIndex); + } else { + auto item = epub->getTocItem(tocIndex); + renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (i % PAGE_ITEMS) * 30, item.title.c_str(), + i != selectorIndex); + } + } + + renderer.displayBuffer(); +} diff --git a/src/screens/EpubReaderChapterSelectionScreen.h b/src/screens/EpubReaderChapterSelectionScreen.h new file mode 100644 index 0000000..8cac4cb --- /dev/null +++ b/src/screens/EpubReaderChapterSelectionScreen.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include +#include + +#include + +#include "Screen.h" + +class EpubReaderChapterSelectionScreen final : public Screen { + std::shared_ptr epub; + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + int currentSpineIndex = 0; + int selectorIndex = 0; + bool updateRequired = false; + const std::function onGoBack; + const std::function onSelectSpineIndex; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void renderScreen(); + + public: + explicit EpubReaderChapterSelectionScreen(GfxRenderer& renderer, InputManager& inputManager, + const std::shared_ptr& epub, const int currentSpineIndex, + const std::function& onGoBack, + const std::function& onSelectSpineIndex) + : Screen(renderer, inputManager), + epub(epub), + currentSpineIndex(currentSpineIndex), + onGoBack(onGoBack), + onSelectSpineIndex(onSelectSpineIndex) {} + void onEnter() override; + void onExit() override; + void handleInput() override; +}; diff --git a/src/screens/EpubReaderScreen.cpp b/src/screens/EpubReaderScreen.cpp index f9ef87f..d0a3103 100644 --- a/src/screens/EpubReaderScreen.cpp +++ b/src/screens/EpubReaderScreen.cpp @@ -5,6 +5,7 @@ #include #include "Battery.h" +#include "EpubReaderChapterSelectionScreen.h" #include "config.h" constexpr int PAGES_PER_REFRESH = 15; @@ -65,6 +66,37 @@ void EpubReaderScreen::onExit() { } void EpubReaderScreen::handleInput() { + // Pass input responsibility to sub screen if exists + if (subScreen) { + subScreen->handleInput(); + return; + } + + // Enter chapter selection screen + if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { + // Don't start screen transition while rendering + xSemaphoreTake(renderingMutex, portMAX_DELAY); + subScreen.reset(new EpubReaderChapterSelectionScreen( + this->renderer, this->inputManager, epub, currentSpineIndex, + [this] { + subScreen->onExit(); + subScreen.reset(); + updateRequired = true; + }, + [this](const int newSpineIndex) { + if (currentSpineIndex != newSpineIndex) { + currentSpineIndex = newSpineIndex; + nextPageNumber = 0; + section.reset(); + } + subScreen->onExit(); + subScreen.reset(); + updateRequired = true; + })); + subScreen->onEnter(); + xSemaphoreGive(renderingMutex); + } + if (inputManager.wasPressed(InputManager::BTN_BACK)) { onGoHome(); return; diff --git a/src/screens/EpubReaderScreen.h b/src/screens/EpubReaderScreen.h index 5b00ad8..4ef8bef 100644 --- a/src/screens/EpubReaderScreen.h +++ b/src/screens/EpubReaderScreen.h @@ -12,6 +12,7 @@ class EpubReaderScreen final : public Screen { std::unique_ptr
section = nullptr; TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; + std::unique_ptr subScreen = nullptr; int currentSpineIndex = 0; int nextPageNumber = 0; int pagesUntilFullRefresh = 0;