diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index 09abd08..01bb3ac 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -3,7 +3,9 @@ #include #include +namespace { constexpr uint8_t PAGE_FILE_VERSION = 3; +} void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); } diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 9c05208..7c9d241 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -9,7 +9,9 @@ #include "Page.h" #include "parsers/ChapterHtmlSlimParser.h" +namespace { constexpr uint8_t SECTION_FILE_VERSION = 5; +} void Section::onPageComplete(std::unique_ptr page) { const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin"; diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 8d3a40e..1f8c3bd 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -10,9 +10,11 @@ // Initialize the static instance CrossPointSettings CrossPointSettings::instance; +namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_COUNT = 2; constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin"; +} // namespace bool CrossPointSettings::saveToFile() const { // Make sure the directory exists diff --git a/src/CrossPointState.cpp b/src/CrossPointState.cpp index b0df9d9..dd96593 100644 --- a/src/CrossPointState.cpp +++ b/src/CrossPointState.cpp @@ -6,8 +6,12 @@ #include +namespace { constexpr uint8_t STATE_FILE_VERSION = 1; constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin"; +} // namespace + +CrossPointState CrossPointState::instance; bool CrossPointState::saveToFile() const { std::ofstream outputFile(STATE_FILE); diff --git a/src/CrossPointState.h b/src/CrossPointState.h index 35b928d..f060a0c 100644 --- a/src/CrossPointState.h +++ b/src/CrossPointState.h @@ -3,11 +3,20 @@ #include class CrossPointState { + // Static instance + static CrossPointState instance; + public: std::string openEpubPath; ~CrossPointState() = default; + // Get singleton instance + static CrossPointState& getInstance() { return instance; } + bool saveToFile() const; bool loadFromFile(); }; + +// Helper macro to access settings +#define APP_STATE CrossPointState::getInstance() diff --git a/src/activities/Activity.h b/src/activities/Activity.h new file mode 100644 index 0000000..28017f7 --- /dev/null +++ b/src/activities/Activity.h @@ -0,0 +1,18 @@ +#pragma once +#include + +class GfxRenderer; + +class Activity { + protected: + GfxRenderer& renderer; + InputManager& inputManager; + + public: + explicit Activity(GfxRenderer& renderer, InputManager& inputManager) + : renderer(renderer), inputManager(inputManager) {} + virtual ~Activity() = default; + virtual void onEnter() {} + virtual void onExit() {} + virtual void loop() {} +}; diff --git a/src/activities/ActivityWithSubactivity.cpp b/src/activities/ActivityWithSubactivity.cpp new file mode 100644 index 0000000..56dccd9 --- /dev/null +++ b/src/activities/ActivityWithSubactivity.cpp @@ -0,0 +1,21 @@ +#include "ActivityWithSubactivity.h" + +void ActivityWithSubactivity::exitActivity() { + if (subActivity) { + subActivity->onExit(); + subActivity.reset(); + } +} + +void ActivityWithSubactivity::enterNewActivity(Activity* activity) { + subActivity.reset(activity); + subActivity->onEnter(); +} + +void ActivityWithSubactivity::loop() { + if (subActivity) { + subActivity->loop(); + } +} + +void ActivityWithSubactivity::onExit() { exitActivity(); } diff --git a/src/activities/ActivityWithSubactivity.h b/src/activities/ActivityWithSubactivity.h new file mode 100644 index 0000000..b3a6873 --- /dev/null +++ b/src/activities/ActivityWithSubactivity.h @@ -0,0 +1,17 @@ +#pragma once +#include + +#include "Activity.h" + +class ActivityWithSubactivity : public Activity { + protected: + std::unique_ptr subActivity = nullptr; + void exitActivity(); + void enterNewActivity(Activity* activity); + + public: + explicit ActivityWithSubactivity(GfxRenderer& renderer, InputManager& inputManager) + : Activity(renderer, inputManager) {} + void loop() override; + void onExit() override; +}; diff --git a/src/screens/BootLogoScreen.cpp b/src/activities/boot_sleep/BootActivity.cpp similarity index 90% rename from src/screens/BootLogoScreen.cpp rename to src/activities/boot_sleep/BootActivity.cpp index 0ec2c10..a76cb7c 100644 --- a/src/screens/BootLogoScreen.cpp +++ b/src/activities/boot_sleep/BootActivity.cpp @@ -1,11 +1,11 @@ -#include "BootLogoScreen.h" +#include "BootActivity.h" #include #include "config.h" #include "images/CrossLarge.h" -void BootLogoScreen::onEnter() { +void BootActivity::onEnter() { const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageHeight = GfxRenderer::getScreenHeight(); diff --git a/src/activities/boot_sleep/BootActivity.h b/src/activities/boot_sleep/BootActivity.h new file mode 100644 index 0000000..dd647ce --- /dev/null +++ b/src/activities/boot_sleep/BootActivity.h @@ -0,0 +1,8 @@ +#pragma once +#include "../Activity.h" + +class BootActivity final : public Activity { + public: + explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {} + void onEnter() override; +}; diff --git a/src/screens/SleepScreen.cpp b/src/activities/boot_sleep/SleepActivity.cpp similarity index 92% rename from src/screens/SleepScreen.cpp rename to src/activities/boot_sleep/SleepActivity.cpp index b280125..2abe91e 100644 --- a/src/screens/SleepScreen.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -1,4 +1,4 @@ -#include "SleepScreen.h" +#include "SleepActivity.h" #include @@ -6,7 +6,7 @@ #include "config.h" #include "images/CrossLarge.h" -void SleepScreen::onEnter() { +void SleepActivity::onEnter() { const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageHeight = GfxRenderer::getScreenHeight(); diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h new file mode 100644 index 0000000..1626481 --- /dev/null +++ b/src/activities/boot_sleep/SleepActivity.h @@ -0,0 +1,8 @@ +#pragma once +#include "../Activity.h" + +class SleepActivity final : public Activity { + public: + explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {} + void onEnter() override; +}; diff --git a/src/screens/HomeScreen.cpp b/src/activities/home/HomeActivity.cpp similarity index 86% rename from src/screens/HomeScreen.cpp rename to src/activities/home/HomeActivity.cpp index 168e0fc..19b30c0 100644 --- a/src/screens/HomeScreen.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -1,16 +1,20 @@ -#include "HomeScreen.h" +#include "HomeActivity.h" #include #include #include "config.h" -void HomeScreen::taskTrampoline(void* param) { - auto* self = static_cast(param); +namespace { +constexpr int menuItemCount = 2; +} + +void HomeActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); self->displayTaskLoop(); } -void HomeScreen::onEnter() { +void HomeActivity::onEnter() { renderingMutex = xSemaphoreCreateMutex(); selectorIndex = 0; @@ -18,7 +22,7 @@ void HomeScreen::onEnter() { // Trigger first update updateRequired = true; - xTaskCreate(&HomeScreen::taskTrampoline, "HomeScreenTask", + xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask", 2048, // Stack size this, // Parameters 1, // Priority @@ -26,7 +30,7 @@ void HomeScreen::onEnter() { ); } -void HomeScreen::onExit() { +void HomeActivity::onExit() { // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { @@ -37,7 +41,7 @@ void HomeScreen::onExit() { renderingMutex = nullptr; } -void HomeScreen::handleInput() { +void HomeActivity::loop() { const bool prevPressed = inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); const bool nextPressed = @@ -45,7 +49,7 @@ void HomeScreen::handleInput() { if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { if (selectorIndex == 0) { - onFileSelectionOpen(); + onReaderOpen(); } else if (selectorIndex == 1) { onSettingsOpen(); } @@ -58,7 +62,7 @@ void HomeScreen::handleInput() { } } -void HomeScreen::displayTaskLoop() { +void HomeActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; @@ -70,7 +74,7 @@ void HomeScreen::displayTaskLoop() { } } -void HomeScreen::render() const { +void HomeActivity::render() const { renderer.clearScreen(); const auto pageWidth = GfxRenderer::getScreenWidth(); diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h new file mode 100644 index 0000000..7f6ac4d --- /dev/null +++ b/src/activities/home/HomeActivity.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include + +#include + +#include "../Activity.h" + +class HomeActivity final : public Activity { + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + int selectorIndex = 0; + bool updateRequired = false; + const std::function onReaderOpen; + const std::function onSettingsOpen; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render() const; + + public: + explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onReaderOpen, + const std::function& onSettingsOpen) + : Activity(renderer, inputManager), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen) {} + void onEnter() override; + void onExit() override; + void loop() override; +}; diff --git a/src/screens/EpubReaderScreen.cpp b/src/activities/reader/EpubReaderActivity.cpp similarity index 90% rename from src/screens/EpubReaderScreen.cpp rename to src/activities/reader/EpubReaderActivity.cpp index e4c0969..bf2eaf7 100644 --- a/src/screens/EpubReaderScreen.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -1,4 +1,4 @@ -#include "EpubReaderScreen.h" +#include "EpubReaderActivity.h" #include #include @@ -6,23 +6,25 @@ #include "Battery.h" #include "CrossPointSettings.h" -#include "EpubReaderChapterSelectionScreen.h" +#include "EpubReaderChapterSelectionActivity.h" #include "config.h" -constexpr int PAGES_PER_REFRESH = 15; -constexpr unsigned long SKIP_CHAPTER_MS = 700; +namespace { +constexpr int pagesPerRefresh = 15; +constexpr unsigned long skipChapterMs = 700; constexpr float lineCompression = 0.95f; constexpr int marginTop = 8; constexpr int marginRight = 10; constexpr int marginBottom = 22; constexpr int marginLeft = 10; +} // namespace -void EpubReaderScreen::taskTrampoline(void* param) { - auto* self = static_cast(param); +void EpubReaderActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); self->displayTaskLoop(); } -void EpubReaderScreen::onEnter() { +void EpubReaderActivity::onEnter() { if (!epub) { return; } @@ -44,7 +46,7 @@ void EpubReaderScreen::onEnter() { // Trigger first update updateRequired = true; - xTaskCreate(&EpubReaderScreen::taskTrampoline, "EpubReaderScreenTask", + xTaskCreate(&EpubReaderActivity::taskTrampoline, "EpubReaderActivityTask", 8192, // Stack size this, // Parameters 1, // Priority @@ -52,7 +54,7 @@ void EpubReaderScreen::onEnter() { ); } -void EpubReaderScreen::onExit() { +void EpubReaderActivity::onExit() { // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { @@ -65,22 +67,22 @@ void EpubReaderScreen::onExit() { epub.reset(); } -void EpubReaderScreen::handleInput() { - // Pass input responsibility to sub screen if exists - if (subScreen) { - subScreen->handleInput(); +void EpubReaderActivity::loop() { + // Pass input responsibility to sub activity if exists + if (subAcitivity) { + subAcitivity->loop(); return; } - // Enter chapter selection screen + // Enter chapter selection activity if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { - // Don't start screen transition while rendering + // Don't start activity transition while rendering xSemaphoreTake(renderingMutex, portMAX_DELAY); - subScreen.reset(new EpubReaderChapterSelectionScreen( + subAcitivity.reset(new EpubReaderChapterSelectionActivity( this->renderer, this->inputManager, epub, currentSpineIndex, [this] { - subScreen->onExit(); - subScreen.reset(); + subAcitivity->onExit(); + subAcitivity.reset(); updateRequired = true; }, [this](const int newSpineIndex) { @@ -89,11 +91,11 @@ void EpubReaderScreen::handleInput() { nextPageNumber = 0; section.reset(); } - subScreen->onExit(); - subScreen.reset(); + subAcitivity->onExit(); + subAcitivity.reset(); updateRequired = true; })); - subScreen->onEnter(); + subAcitivity->onEnter(); xSemaphoreGive(renderingMutex); } @@ -119,7 +121,7 @@ void EpubReaderScreen::handleInput() { return; } - const bool skipChapter = inputManager.getHeldTime() > SKIP_CHAPTER_MS; + const bool skipChapter = inputManager.getHeldTime() > skipChapterMs; if (skipChapter) { // We don't want to delete the section mid-render, so grab the semaphore @@ -165,7 +167,7 @@ void EpubReaderScreen::handleInput() { } } -void EpubReaderScreen::displayTaskLoop() { +void EpubReaderActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; @@ -178,7 +180,7 @@ void EpubReaderScreen::displayTaskLoop() { } // TODO: Failure handling -void EpubReaderScreen::renderScreen() { +void EpubReaderActivity::renderScreen() { if (!epub) { return; } @@ -285,12 +287,12 @@ void EpubReaderScreen::renderScreen() { f.close(); } -void EpubReaderScreen::renderContents(std::unique_ptr page) { +void EpubReaderActivity::renderContents(std::unique_ptr page) { page->render(renderer, READER_FONT_ID); renderStatusBar(); if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(EInkDisplay::HALF_REFRESH); - pagesUntilFullRefresh = PAGES_PER_REFRESH; + pagesUntilFullRefresh = pagesPerRefresh; } else { renderer.displayBuffer(); pagesUntilFullRefresh--; @@ -322,7 +324,7 @@ void EpubReaderScreen::renderContents(std::unique_ptr page) { renderer.restoreBwBuffer(); } -void EpubReaderScreen::renderStatusBar() const { +void EpubReaderActivity::renderStatusBar() const { constexpr auto textY = 776; // Calculate progress in book diff --git a/src/screens/EpubReaderScreen.h b/src/activities/reader/EpubReaderActivity.h similarity index 63% rename from src/screens/EpubReaderScreen.h rename to src/activities/reader/EpubReaderActivity.h index 0042c29..8eeddc1 100644 --- a/src/screens/EpubReaderScreen.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -5,14 +5,14 @@ #include #include -#include "Screen.h" +#include "../Activity.h" -class EpubReaderScreen final : public Screen { +class EpubReaderActivity final : public Activity { std::shared_ptr epub; std::unique_ptr
section = nullptr; TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; - std::unique_ptr subScreen = nullptr; + std::unique_ptr subAcitivity = nullptr; int currentSpineIndex = 0; int nextPageNumber = 0; int pagesUntilFullRefresh = 0; @@ -26,10 +26,10 @@ class EpubReaderScreen final : public Screen { void renderStatusBar() const; public: - explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr epub, - const std::function& onGoBack) - : Screen(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {} + explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr epub, + const std::function& onGoBack) + : Activity(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {} void onEnter() override; void onExit() override; - void handleInput() override; + void loop() override; }; diff --git a/src/screens/EpubReaderChapterSelectionScreen.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp similarity index 84% rename from src/screens/EpubReaderChapterSelectionScreen.cpp rename to src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 26688a4..f4dfc37 100644 --- a/src/screens/EpubReaderChapterSelectionScreen.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -1,4 +1,4 @@ -#include "EpubReaderChapterSelectionScreen.h" +#include "EpubReaderChapterSelectionActivity.h" #include #include @@ -8,12 +8,12 @@ constexpr int PAGE_ITEMS = 24; constexpr int SKIP_PAGE_MS = 700; -void EpubReaderChapterSelectionScreen::taskTrampoline(void* param) { - auto* self = static_cast(param); +void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); self->displayTaskLoop(); } -void EpubReaderChapterSelectionScreen::onEnter() { +void EpubReaderChapterSelectionActivity::onEnter() { if (!epub) { return; } @@ -23,7 +23,7 @@ void EpubReaderChapterSelectionScreen::onEnter() { // Trigger first update updateRequired = true; - xTaskCreate(&EpubReaderChapterSelectionScreen::taskTrampoline, "EpubReaderChapterSelectionScreenTask", + xTaskCreate(&EpubReaderChapterSelectionActivity::taskTrampoline, "EpubReaderChapterSelectionActivityTask", 2048, // Stack size this, // Parameters 1, // Priority @@ -31,7 +31,7 @@ void EpubReaderChapterSelectionScreen::onEnter() { ); } -void EpubReaderChapterSelectionScreen::onExit() { +void EpubReaderChapterSelectionActivity::onExit() { // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { @@ -42,7 +42,7 @@ void EpubReaderChapterSelectionScreen::onExit() { renderingMutex = nullptr; } -void EpubReaderChapterSelectionScreen::handleInput() { +void EpubReaderChapterSelectionActivity::loop() { const bool prevReleased = inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT); const bool nextReleased = @@ -72,7 +72,7 @@ void EpubReaderChapterSelectionScreen::handleInput() { } } -void EpubReaderChapterSelectionScreen::displayTaskLoop() { +void EpubReaderChapterSelectionActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; @@ -84,7 +84,7 @@ void EpubReaderChapterSelectionScreen::displayTaskLoop() { } } -void EpubReaderChapterSelectionScreen::renderScreen() { +void EpubReaderChapterSelectionActivity::renderScreen() { renderer.clearScreen(); const auto pageWidth = renderer.getScreenWidth(); diff --git a/src/screens/EpubReaderChapterSelectionScreen.h b/src/activities/reader/EpubReaderChapterSelectionActivity.h similarity index 57% rename from src/screens/EpubReaderChapterSelectionScreen.h rename to src/activities/reader/EpubReaderChapterSelectionActivity.h index 8cac4cb..b25ef6f 100644 --- a/src/screens/EpubReaderChapterSelectionScreen.h +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.h @@ -6,9 +6,9 @@ #include -#include "Screen.h" +#include "../Activity.h" -class EpubReaderChapterSelectionScreen final : public Screen { +class EpubReaderChapterSelectionActivity final : public Activity { std::shared_ptr epub; TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; @@ -23,16 +23,16 @@ class EpubReaderChapterSelectionScreen final : public Screen { 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), + explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, + const std::shared_ptr& epub, const int currentSpineIndex, + const std::function& onGoBack, + const std::function& onSelectSpineIndex) + : Activity(renderer, inputManager), epub(epub), currentSpineIndex(currentSpineIndex), onGoBack(onGoBack), onSelectSpineIndex(onSelectSpineIndex) {} void onEnter() override; void onExit() override; - void handleInput() override; + void loop() override; }; diff --git a/src/screens/FileSelectionScreen.cpp b/src/activities/reader/FileSelectionActivity.cpp similarity index 88% rename from src/screens/FileSelectionScreen.cpp rename to src/activities/reader/FileSelectionActivity.cpp index ce4d000..9e665cb 100644 --- a/src/screens/FileSelectionScreen.cpp +++ b/src/activities/reader/FileSelectionActivity.cpp @@ -1,4 +1,4 @@ -#include "FileSelectionScreen.h" +#include "FileSelectionActivity.h" #include #include @@ -15,12 +15,12 @@ void sortFileList(std::vector& strs) { }); } -void FileSelectionScreen::taskTrampoline(void* param) { - auto* self = static_cast(param); +void FileSelectionActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); self->displayTaskLoop(); } -void FileSelectionScreen::loadFiles() { +void FileSelectionActivity::loadFiles() { files.clear(); selectorIndex = 0; auto root = SD.open(basepath.c_str()); @@ -42,7 +42,7 @@ void FileSelectionScreen::loadFiles() { sortFileList(files); } -void FileSelectionScreen::onEnter() { +void FileSelectionActivity::onEnter() { renderingMutex = xSemaphoreCreateMutex(); basepath = "/"; @@ -52,7 +52,7 @@ void FileSelectionScreen::onEnter() { // Trigger first update updateRequired = true; - xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask", + xTaskCreate(&FileSelectionActivity::taskTrampoline, "FileSelectionActivityTask", 2048, // Stack size this, // Parameters 1, // Priority @@ -60,7 +60,7 @@ void FileSelectionScreen::onEnter() { ); } -void FileSelectionScreen::onExit() { +void FileSelectionActivity::onExit() { // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { @@ -72,7 +72,7 @@ void FileSelectionScreen::onExit() { files.clear(); } -void FileSelectionScreen::handleInput() { +void FileSelectionActivity::loop() { const bool prevPressed = inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); const bool nextPressed = @@ -110,7 +110,7 @@ void FileSelectionScreen::handleInput() { } } -void FileSelectionScreen::displayTaskLoop() { +void FileSelectionActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; @@ -122,7 +122,7 @@ void FileSelectionScreen::displayTaskLoop() { } } -void FileSelectionScreen::render() const { +void FileSelectionActivity::render() const { renderer.clearScreen(); const auto pageWidth = GfxRenderer::getScreenWidth(); diff --git a/src/screens/FileSelectionScreen.h b/src/activities/reader/FileSelectionActivity.h similarity index 60% rename from src/screens/FileSelectionScreen.h rename to src/activities/reader/FileSelectionActivity.h index aaa076d..ff1a922 100644 --- a/src/screens/FileSelectionScreen.h +++ b/src/activities/reader/FileSelectionActivity.h @@ -7,9 +7,9 @@ #include #include -#include "Screen.h" +#include "../Activity.h" -class FileSelectionScreen final : public Screen { +class FileSelectionActivity final : public Activity { TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; std::string basepath = "/"; @@ -25,11 +25,11 @@ class FileSelectionScreen final : public Screen { void loadFiles(); public: - explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager, - const std::function& onSelect, - const std::function& onGoHome) - : Screen(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {} + explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager, + const std::function& onSelect, + const std::function& onGoHome) + : Activity(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; - void handleInput() override; + void loop() override; }; diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp new file mode 100644 index 0000000..099d7e2 --- /dev/null +++ b/src/activities/reader/ReaderActivity.cpp @@ -0,0 +1,68 @@ +#include "ReaderActivity.h" + +#include + +#include "CrossPointState.h" +#include "Epub.h" +#include "EpubReaderActivity.h" +#include "FileSelectionActivity.h" +#include "activities/util/FullScreenMessageActivity.h" + +std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { + if (!SD.exists(path.c_str())) { + Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); + return nullptr; + } + + auto epub = std::unique_ptr(new Epub(path, "/.crosspoint")); + if (epub->load()) { + return epub; + } + + Serial.printf("[%lu] [ ] Failed to load epub\n", millis()); + return nullptr; +} + +void ReaderActivity::onSelectEpubFile(const std::string& path) { + exitActivity(); + enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading...")); + + auto epub = loadEpub(path); + if (epub) { + APP_STATE.openEpubPath = path; + APP_STATE.saveToFile(); + onGoToEpubReader(std::move(epub)); + } else { + exitActivity(); + enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load epub", REGULAR, + EInkDisplay::HALF_REFRESH)); + delay(2000); + onGoToFileSelection(); + } +} + +void ReaderActivity::onGoToFileSelection() { + exitActivity(); + enterNewActivity(new FileSelectionActivity( + renderer, inputManager, [this](const std::string& path) { onSelectEpubFile(path); }, onGoBack)); +} + +void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { + exitActivity(); + enterNewActivity(new EpubReaderActivity(renderer, inputManager, std::move(epub), [this] { onGoToFileSelection(); })); +} + +void ReaderActivity::onEnter() { + if (initialEpubPath.empty()) { + onGoToFileSelection(); + return; + } + + auto epub = loadEpub(initialEpubPath); + if (!epub) { + onGoBack(); + return; + } + + onGoToEpubReader(std::move(epub)); +} diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h new file mode 100644 index 0000000..a68cd89 --- /dev/null +++ b/src/activities/reader/ReaderActivity.h @@ -0,0 +1,24 @@ +#pragma once +#include + +#include "../ActivityWithSubactivity.h" + +class Epub; + +class ReaderActivity final : public ActivityWithSubactivity { + std::string initialEpubPath; + const std::function onGoBack; + static std::unique_ptr loadEpub(const std::string& path); + + void onSelectEpubFile(const std::string& path); + void onGoToFileSelection(); + void onGoToEpubReader(std::unique_ptr epub); + + public: + explicit ReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::string initialEpubPath, + const std::function& onGoBack) + : ActivityWithSubactivity(renderer, inputManager), + initialEpubPath(std::move(initialEpubPath)), + onGoBack(onGoBack) {} + void onEnter() override; +}; diff --git a/src/screens/SettingsScreen.cpp b/src/activities/settings/SettingsActivity.cpp similarity index 87% rename from src/screens/SettingsScreen.cpp rename to src/activities/settings/SettingsActivity.cpp index e970752..3b287d6 100644 --- a/src/screens/SettingsScreen.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -1,4 +1,4 @@ -#include "SettingsScreen.h" +#include "SettingsActivity.h" #include @@ -7,16 +7,16 @@ // Define the static settings list -const SettingInfo SettingsScreen::settingsList[SettingsScreen::settingsCount] = { +const SettingInfo SettingsActivity::settingsList[settingsCount] = { {"White Sleep Screen", &CrossPointSettings::whiteSleepScreen}, {"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing}}; -void SettingsScreen::taskTrampoline(void* param) { - auto* self = static_cast(param); +void SettingsActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); self->displayTaskLoop(); } -void SettingsScreen::onEnter() { +void SettingsActivity::onEnter() { renderingMutex = xSemaphoreCreateMutex(); // Reset selection to first item @@ -25,7 +25,7 @@ void SettingsScreen::onEnter() { // Trigger first update updateRequired = true; - xTaskCreate(&SettingsScreen::taskTrampoline, "SettingsScreenTask", + xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask", 2048, // Stack size this, // Parameters 1, // Priority @@ -33,7 +33,7 @@ void SettingsScreen::onEnter() { ); } -void SettingsScreen::onExit() { +void SettingsActivity::onExit() { // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { @@ -44,7 +44,7 @@ void SettingsScreen::onExit() { renderingMutex = nullptr; } -void SettingsScreen::handleInput() { +void SettingsActivity::loop() { // Handle actions with early return if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { toggleCurrentSetting(); @@ -70,7 +70,7 @@ void SettingsScreen::handleInput() { } } -void SettingsScreen::toggleCurrentSetting() { +void SettingsActivity::toggleCurrentSetting() { // Validate index if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) { return; @@ -84,7 +84,7 @@ void SettingsScreen::toggleCurrentSetting() { SETTINGS.saveToFile(); } -void SettingsScreen::displayTaskLoop() { +void SettingsActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; @@ -96,7 +96,7 @@ void SettingsScreen::displayTaskLoop() { } } -void SettingsScreen::render() const { +void SettingsActivity::render() const { renderer.clearScreen(); const auto pageWidth = GfxRenderer::getScreenWidth(); diff --git a/src/screens/SettingsScreen.h b/src/activities/settings/SettingsActivity.h similarity index 77% rename from src/screens/SettingsScreen.h rename to src/activities/settings/SettingsActivity.h index 8de45b8..b7ace22 100644 --- a/src/screens/SettingsScreen.h +++ b/src/activities/settings/SettingsActivity.h @@ -7,7 +7,7 @@ #include #include -#include "Screen.h" +#include "../Activity.h" class CrossPointSettings; @@ -17,7 +17,7 @@ struct SettingInfo { uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings }; -class SettingsScreen final : public Screen { +class SettingsActivity final : public Activity { TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; bool updateRequired = false; @@ -34,9 +34,9 @@ class SettingsScreen final : public Screen { void toggleCurrentSetting(); public: - explicit SettingsScreen(GfxRenderer& renderer, InputManager& inputManager, const std::function& onGoHome) - : Screen(renderer, inputManager), onGoHome(onGoHome) {} + explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onGoHome) + : Activity(renderer, inputManager), onGoHome(onGoHome) {} void onEnter() override; void onExit() override; - void handleInput() override; + void loop() override; }; diff --git a/src/screens/FullScreenMessageScreen.cpp b/src/activities/util/FullScreenMessageActivity.cpp similarity index 79% rename from src/screens/FullScreenMessageScreen.cpp rename to src/activities/util/FullScreenMessageActivity.cpp index 19787a7..a56952f 100644 --- a/src/screens/FullScreenMessageScreen.cpp +++ b/src/activities/util/FullScreenMessageActivity.cpp @@ -1,10 +1,10 @@ -#include "FullScreenMessageScreen.h" +#include "FullScreenMessageActivity.h" #include #include "config.h" -void FullScreenMessageScreen::onEnter() { +void FullScreenMessageActivity::onEnter() { const auto height = renderer.getLineHeight(UI_FONT_ID); const auto top = (GfxRenderer::getScreenHeight() - height) / 2; diff --git a/src/activities/util/FullScreenMessageActivity.h b/src/activities/util/FullScreenMessageActivity.h new file mode 100644 index 0000000..3180ddb --- /dev/null +++ b/src/activities/util/FullScreenMessageActivity.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +#include +#include + +#include "../Activity.h" + +class FullScreenMessageActivity final : public Activity { + std::string text; + EpdFontStyle style; + EInkDisplay::RefreshMode refreshMode; + + public: + explicit FullScreenMessageActivity(GfxRenderer& renderer, InputManager& inputManager, std::string text, + const EpdFontStyle style = REGULAR, + const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) + : Activity(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {} + void onEnter() override; +}; diff --git a/src/main.cpp b/src/main.cpp index 00a4b45..39c5bd0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,14 +16,13 @@ #include "Battery.h" #include "CrossPointSettings.h" #include "CrossPointState.h" +#include "activities/boot_sleep/BootActivity.h" +#include "activities/boot_sleep/SleepActivity.h" +#include "activities/home/HomeActivity.h" +#include "activities/reader/ReaderActivity.h" +#include "activities/settings/SettingsActivity.h" +#include "activities/util/FullScreenMessageActivity.h" #include "config.h" -#include "screens/BootLogoScreen.h" -#include "screens/EpubReaderScreen.h" -#include "screens/FileSelectionScreen.h" -#include "screens/FullScreenMessageScreen.h" -#include "screens/HomeScreen.h" -#include "screens/SettingsScreen.h" -#include "screens/SleepScreen.h" #define SPI_FQ 40000000 // Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) @@ -42,8 +41,7 @@ EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); InputManager inputManager; GfxRenderer renderer(einkDisplay); -Screen* currentScreen; -CrossPointState appState; +Activity* currentActivity; // Fonts EpdFont bookerlyFont(&bookerly_2b); @@ -67,31 +65,16 @@ constexpr unsigned long POWER_BUTTON_SLEEP_MS = 500; // Auto-sleep timeout (10 minutes of inactivity) constexpr unsigned long AUTO_SLEEP_TIMEOUT_MS = 10 * 60 * 1000; -std::unique_ptr loadEpub(const std::string& path) { - if (!SD.exists(path.c_str())) { - Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); - return nullptr; - } - - auto epub = std::unique_ptr(new Epub(path, "/.crosspoint")); - if (epub->load()) { - return epub; - } - - Serial.printf("[%lu] [ ] Failed to load epub\n", millis()); - return nullptr; -} - -void exitScreen() { - if (currentScreen) { - currentScreen->onExit(); - delete currentScreen; +void exitActivity() { + if (currentActivity) { + currentActivity->onExit(); + delete currentActivity; } } -void enterNewScreen(Screen* screen) { - currentScreen = screen; - currentScreen->onEnter(); +void enterNewActivity(Activity* activity) { + currentActivity = activity; + currentActivity->onEnter(); } // Verify long press on wake-up from deep sleep @@ -135,8 +118,8 @@ void waitForPowerRelease() { // Enter deep sleep mode void enterDeepSleep() { - exitScreen(); - enterNewScreen(new SleepScreen(renderer, inputManager)); + exitActivity(); + enterNewActivity(new SleepActivity(renderer, inputManager)); Serial.printf("[%lu] [ ] Power button released after a long press. Entering deep sleep.\n", millis()); delay(1000); // Allow Serial buffer to empty and display to update @@ -151,39 +134,20 @@ void enterDeepSleep() { } void onGoHome(); -void onGoToFileSelection(); -void onSelectEpubFile(const std::string& path) { - exitScreen(); - enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading...")); - - auto epub = loadEpub(path); - if (epub) { - appState.openEpubPath = path; - appState.saveToFile(); - exitScreen(); - enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoToFileSelection)); - } else { - exitScreen(); - enterNewScreen( - new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH)); - delay(2000); - onGoToFileSelection(); - } -} - -void onGoToFileSelection() { - exitScreen(); - enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoHome)); +void onGoToReader(const std::string& initialEpubPath) { + exitActivity(); + enterNewActivity(new ReaderActivity(renderer, inputManager, initialEpubPath, onGoHome)); } +void onGoToReaderHome() { onGoToReader(std::string()); } void onGoToSettings() { - exitScreen(); - enterNewScreen(new SettingsScreen(renderer, inputManager, onGoHome)); + exitActivity(); + enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome)); } void onGoHome() { - exitScreen(); - enterNewScreen(new HomeScreen(renderer, inputManager, onGoToFileSelection, onGoToSettings)); + exitActivity(); + enterNewActivity(new HomeActivity(renderer, inputManager, onGoToReaderHome, onGoToSettings)); } void setup() { @@ -209,27 +173,20 @@ void setup() { renderer.insertFont(SMALL_FONT_ID, smallFontFamily); Serial.printf("[%lu] [ ] Fonts setup\n", millis()); - exitScreen(); - enterNewScreen(new BootLogoScreen(renderer, inputManager)); + exitActivity(); + enterNewActivity(new BootActivity(renderer, inputManager)); // SD Card Initialization SD.begin(SD_SPI_CS, SPI, SPI_FQ); SETTINGS.loadFromFile(); - appState.loadFromFile(); - if (!appState.openEpubPath.empty()) { - auto epub = loadEpub(appState.openEpubPath); - if (epub) { - exitScreen(); - enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoHome)); - // Ensure we're not still holding the power button before leaving setup - waitForPowerRelease(); - return; - } + APP_STATE.loadFromFile(); + if (APP_STATE.openEpubPath.empty()) { + onGoHome(); + } else { + onGoToReader(APP_STATE.openEpubPath); } - onGoHome(); - // Ensure we're not still holding the power button before leaving setup waitForPowerRelease(); } @@ -265,7 +222,7 @@ void loop() { return; } - if (currentScreen) { - currentScreen->handleInput(); + if (currentActivity) { + currentActivity->loop(); } } diff --git a/src/screens/BootLogoScreen.h b/src/screens/BootLogoScreen.h deleted file mode 100644 index 503afac..0000000 --- a/src/screens/BootLogoScreen.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "Screen.h" - -class BootLogoScreen final : public Screen { - public: - explicit BootLogoScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} - void onEnter() override; -}; diff --git a/src/screens/FullScreenMessageScreen.h b/src/screens/FullScreenMessageScreen.h deleted file mode 100644 index e90abb6..0000000 --- a/src/screens/FullScreenMessageScreen.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include -#include - -#include -#include - -#include "Screen.h" - -class FullScreenMessageScreen final : public Screen { - std::string text; - EpdFontStyle style; - EInkDisplay::RefreshMode refreshMode; - - public: - explicit FullScreenMessageScreen(GfxRenderer& renderer, InputManager& inputManager, std::string text, - const EpdFontStyle style = REGULAR, - const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) - : Screen(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {} - void onEnter() override; -}; diff --git a/src/screens/HomeScreen.h b/src/screens/HomeScreen.h deleted file mode 100644 index dbdd21b..0000000 --- a/src/screens/HomeScreen.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include -#include - -#include - -#include "Screen.h" - -class HomeScreen final : public Screen { - TaskHandle_t displayTaskHandle = nullptr; - SemaphoreHandle_t renderingMutex = nullptr; - int selectorIndex = 0; - bool updateRequired = false; - const std::function onFileSelectionOpen; - const std::function onSettingsOpen; - - static constexpr int menuItemCount = 2; - - static void taskTrampoline(void* param); - [[noreturn]] void displayTaskLoop(); - void render() const; - - public: - explicit HomeScreen(GfxRenderer& renderer, InputManager& inputManager, - const std::function& onFileSelectionOpen, const std::function& onSettingsOpen) - : Screen(renderer, inputManager), onFileSelectionOpen(onFileSelectionOpen), onSettingsOpen(onSettingsOpen) {} - void onEnter() override; - void onExit() override; - void handleInput() override; -}; diff --git a/src/screens/Screen.h b/src/screens/Screen.h deleted file mode 100644 index 7e4b866..0000000 --- a/src/screens/Screen.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include - -class GfxRenderer; - -class Screen { - protected: - GfxRenderer& renderer; - InputManager& inputManager; - - public: - explicit Screen(GfxRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {} - virtual ~Screen() = default; - virtual void onEnter() {} - virtual void onExit() {} - virtual void handleInput() {} -}; diff --git a/src/screens/SleepScreen.h b/src/screens/SleepScreen.h deleted file mode 100644 index b280087..0000000 --- a/src/screens/SleepScreen.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include "Screen.h" - -class SleepScreen final : public Screen { - public: - explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} - void onEnter() override; -};