From 7fce5b347dfe62bee5176985548a60d7c6287588 Mon Sep 17 00:00:00 2001 From: cottongin Date: Sat, 24 Jan 2026 03:59:08 -0500 Subject: [PATCH] home screen performance fix --- src/CrossPointState.cpp | 14 ++- src/CrossPointState.h | 2 + src/ScreenComponents.cpp | 35 ++++++ src/ScreenComponents.h | 3 + src/activities/home/HomeActivity.cpp | 120 +++++++++++++------ src/activities/home/HomeActivity.h | 10 +- src/activities/reader/EpubReaderActivity.cpp | 4 +- src/activities/reader/TxtReaderActivity.cpp | 4 +- src/activities/reader/XtcReaderActivity.cpp | 4 +- 9 files changed, 155 insertions(+), 41 deletions(-) diff --git a/src/CrossPointState.cpp b/src/CrossPointState.cpp index 91aa253..829c1ac 100644 --- a/src/CrossPointState.cpp +++ b/src/CrossPointState.cpp @@ -5,7 +5,7 @@ #include namespace { -constexpr uint8_t STATE_FILE_VERSION = 2; +constexpr uint8_t STATE_FILE_VERSION = 3; constexpr char STATE_FILE[] = "/.crosspoint/state.bin"; } // namespace @@ -20,6 +20,9 @@ bool CrossPointState::saveToFile() const { serialization::writePod(outputFile, STATE_FILE_VERSION); serialization::writeString(outputFile, openEpubPath); serialization::writePod(outputFile, lastSleepImage); + // Version 3: add cached book title and author + serialization::writeString(outputFile, openBookTitle); + serialization::writeString(outputFile, openBookAuthor); outputFile.close(); return true; } @@ -45,6 +48,15 @@ bool CrossPointState::loadFromFile() { lastSleepImage = 0; } + // Version 3: read cached book title and author + if (version >= 3) { + serialization::readString(inputFile, openBookTitle); + serialization::readString(inputFile, openBookAuthor); + } else { + openBookTitle.clear(); + openBookAuthor.clear(); + } + inputFile.close(); return true; } diff --git a/src/CrossPointState.h b/src/CrossPointState.h index 87ce4e9..5553eaa 100644 --- a/src/CrossPointState.h +++ b/src/CrossPointState.h @@ -8,6 +8,8 @@ class CrossPointState { public: std::string openEpubPath; + std::string openBookTitle; // Cached title for the current book + std::string openBookAuthor; // Cached author for the current book uint8_t lastSleepImage; ~CrossPointState() = default; diff --git a/src/ScreenComponents.cpp b/src/ScreenComponents.cpp index 2e8d9e7..29da701 100644 --- a/src/ScreenComponents.cpp +++ b/src/ScreenComponents.cpp @@ -42,6 +42,41 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); } +void ScreenComponents::drawBatteryLarge(const GfxRenderer& renderer, const int left, const int top, + const bool showPercentage) { + // Larger battery icon with UI_10 font for bottom button hint area + const uint16_t percentage = battery.readPercentage(); + const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : ""; + renderer.drawText(UI_10_FONT_ID, left + 28, top, percentageText.c_str()); + + // Scaled up battery dimensions (~33% larger) + constexpr int batteryWidth = 20; + constexpr int batteryHeight = 16; + const int x = left; + const int y = top + 6; + + // Top line + renderer.drawLine(x + 1, y, x + batteryWidth - 4, y); + // Bottom line + renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1); + // Left line + renderer.drawLine(x, y + 1, x, y + batteryHeight - 2); + // Battery end (right side with nub) + renderer.drawLine(x + batteryWidth - 3, y + 1, x + batteryWidth - 3, y + batteryHeight - 2); + // Battery nub + renderer.drawPixel(x + batteryWidth - 2, y + 4); + renderer.drawPixel(x + batteryWidth - 2, y + batteryHeight - 5); + renderer.drawLine(x + batteryWidth - 1, y + 5, x + batteryWidth - 1, y + batteryHeight - 6); + + // The +1 is to round up, so that we always fill at least one pixel + int filledWidth = percentage * (batteryWidth - 6) / 100 + 1; + if (filledWidth > batteryWidth - 6) { + filledWidth = batteryWidth - 6; // Ensure we don't overflow + } + + renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4); +} + int ScreenComponents::drawTabBar(const GfxRenderer& renderer, const int y, const std::vector& tabs) { constexpr int tabPadding = 20; // Horizontal padding between tabs constexpr int leftMargin = 20; // Left margin for first tab diff --git a/src/ScreenComponents.h b/src/ScreenComponents.h index 48c40f4..c03adac 100644 --- a/src/ScreenComponents.h +++ b/src/ScreenComponents.h @@ -15,6 +15,9 @@ class ScreenComponents { public: static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true); + // Draw a larger battery icon suitable for bottom button hint area + static void drawBatteryLarge(const GfxRenderer& renderer, int left, int top, bool showPercentage = true); + // Draw a horizontal tab bar with underline indicator for selected tab // Returns the height of the tab bar (for positioning content below) static int drawTabBar(const GfxRenderer& renderer, int y, const std::vector& tabs); diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 0f0e8ed..a193e17 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -18,6 +18,12 @@ #include "fontIds.h" #include "util/StringUtils.h" +// Static member definitions for persistent cover buffer +bool HomeActivity::coverRendered = false; +bool HomeActivity::coverBufferStored = false; +uint8_t* HomeActivity::coverBuffer = nullptr; +std::string HomeActivity::cachedCoverPath; + void HomeActivity::taskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); @@ -41,50 +47,94 @@ void HomeActivity::onEnter() { // Check if OPDS browser URL is configured hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0; - if (hasContinueReading) { - // Extract filename from path for display - lastBookTitle = APP_STATE.openEpubPath; - const size_t lastSlash = lastBookTitle.find_last_of('/'); + if (!hasContinueReading) { + // No book to continue reading - clear any stale cover cache + if (coverBufferStored) { + freeCoverBuffer(); + coverRendered = false; + cachedCoverPath.clear(); + } + } else { + // Extract filename from path for display (fallback if no cached title) + std::string filenameFromPath = APP_STATE.openEpubPath; + const size_t lastSlash = filenameFromPath.find_last_of('/'); if (lastSlash != std::string::npos) { - lastBookTitle = lastBookTitle.substr(lastSlash + 1); + filenameFromPath = filenameFromPath.substr(lastSlash + 1); } - // If epub, try to load the metadata for title/author and cover - if (StringUtils::checkFileExtension(lastBookTitle, ".epub")) { + // Check if we have cached title/author from CrossPointState + const bool hasCachedMetadata = !APP_STATE.openBookTitle.empty(); + + if (hasCachedMetadata) { + // Use cached title and author - no need to load the book file + lastBookTitle = APP_STATE.openBookTitle; + lastBookAuthor = APP_STATE.openBookAuthor; + } else { + // No cached metadata - use filename as fallback + lastBookTitle = filenameFromPath; + } + + // If epub, try to get cover image (and populate cache if needed) + if (StringUtils::checkFileExtension(filenameFromPath, ".epub")) { Epub epub(APP_STATE.openEpubPath, "/.crosspoint"); - epub.load(false); - if (!epub.getTitle().empty()) { - lastBookTitle = std::string(epub.getTitle()); - } - if (!epub.getAuthor().empty()) { - lastBookAuthor = std::string(epub.getAuthor()); + // Only load epub metadata if we don't have cached title/author + if (!hasCachedMetadata) { + epub.load(false); + if (!epub.getTitle().empty()) { + lastBookTitle = std::string(epub.getTitle()); + APP_STATE.openBookTitle = lastBookTitle; + } + if (!epub.getAuthor().empty()) { + lastBookAuthor = std::string(epub.getAuthor()); + APP_STATE.openBookAuthor = lastBookAuthor; + } + // Save updated cache to file + APP_STATE.saveToFile(); } // Try to generate thumbnail image for Continue Reading card if (epub.generateThumbBmp()) { coverBmpPath = epub.getThumbBmpPath(); hasCoverImage = true; } - } else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") || - StringUtils::checkFileExtension(lastBookTitle, ".xtc")) { + } else if (StringUtils::checkFileExtension(filenameFromPath, ".xtch") || + StringUtils::checkFileExtension(filenameFromPath, ".xtc")) { // Handle XTC file Xtc xtc(APP_STATE.openEpubPath, "/.crosspoint"); - if (xtc.load()) { - if (!xtc.getTitle().empty()) { - lastBookTitle = std::string(xtc.getTitle()); + if (!hasCachedMetadata) { + if (xtc.load()) { + if (!xtc.getTitle().empty()) { + lastBookTitle = std::string(xtc.getTitle()); + APP_STATE.openBookTitle = lastBookTitle; + APP_STATE.saveToFile(); + } } - // Try to generate thumbnail image for Continue Reading card - if (xtc.generateThumbBmp()) { - coverBmpPath = xtc.getThumbBmpPath(); - hasCoverImage = true; + // Remove extension from title if we don't have metadata + if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) { + lastBookTitle.resize(lastBookTitle.length() - 5); + APP_STATE.openBookTitle = lastBookTitle; + APP_STATE.saveToFile(); + } else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) { + lastBookTitle.resize(lastBookTitle.length() - 4); + APP_STATE.openBookTitle = lastBookTitle; + APP_STATE.saveToFile(); } } - // Remove extension from title if we don't have metadata - if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) { - lastBookTitle.resize(lastBookTitle.length() - 5); - } else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) { - lastBookTitle.resize(lastBookTitle.length() - 4); + // Try to generate thumbnail image for Continue Reading card + if (xtc.generateThumbBmp()) { + coverBmpPath = xtc.getThumbBmpPath(); + hasCoverImage = true; } } + + // Check if cached cover buffer is still valid (same book) + if (hasCoverImage && coverBufferStored && cachedCoverPath == coverBmpPath) { + // Cover buffer is still valid, mark as rendered to skip reload + coverRendered = true; + } else if (hasCoverImage && coverBufferStored && cachedCoverPath != coverBmpPath) { + // Book changed, invalidate cached buffer + freeCoverBuffer(); + coverRendered = false; + } } selectorIndex = 0; @@ -112,8 +162,8 @@ void HomeActivity::onExit() { vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; - // Free the stored cover buffer if any - freeCoverBuffer(); + // NOTE: Do NOT free cover buffer here - keep it cached for fast re-entry + // The buffer will be freed/replaced when the book changes } bool HomeActivity::storeCoverBuffer() { @@ -291,6 +341,9 @@ void HomeActivity::render() { // Store the buffer with cover image for fast navigation coverBufferStored = storeCoverBuffer(); + if (coverBufferStored) { + cachedCoverPath = coverBmpPath; // Remember which cover is cached + } coverRendered = true; // First render: if selected, draw selection indicators now @@ -552,13 +605,12 @@ void HomeActivity::render() { const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down"); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + // Draw battery in bottom-left where the back button hint would normally be const bool showBatteryPercentage = SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS; - // get percentage so we can align text properly - const uint16_t percentage = battery.readPercentage(); - const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : ""; - const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); - ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage); + constexpr int batteryX = 25; // Align with first button hint position + const int batteryY = pageHeight - 34; // Vertically centered in button hint area + ScreenComponents::drawBatteryLarge(renderer, batteryX, batteryY, showBatteryPercentage); renderer.displayBuffer(); } diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index 5296351..ad5e271 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -15,9 +15,13 @@ class HomeActivity final : public Activity { bool hasContinueReading = false; bool hasOpdsUrl = false; bool hasCoverImage = false; - bool coverRendered = false; // Track if cover has been rendered once - bool coverBufferStored = false; // Track if cover buffer is stored - uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image + + // Static cover buffer - persists across activity changes to avoid reloading from SD + static bool coverRendered; // Track if cover has been rendered once + static bool coverBufferStored; // Track if cover buffer is stored + static uint8_t* coverBuffer; // HomeActivity's own buffer for cover image + static std::string cachedCoverPath; // Path of the cached cover (to detect book changes) + std::string lastBookTitle; std::string lastBookAuthor; std::string coverBmpPath; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index babfb99..832144e 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -120,8 +120,10 @@ void EpubReaderActivity::onEnter() { } } - // Save current epub as last opened epub and add to recent books + // Save current epub as last opened epub and cache title/author for home screen APP_STATE.openEpubPath = epub->getPath(); + APP_STATE.openBookTitle = epub->getTitle(); + APP_STATE.openBookAuthor = epub->getAuthor(); APP_STATE.saveToFile(); RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor()); diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index f3050ad..94442ad 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -97,8 +97,10 @@ void TxtReaderActivity::onEnter() { }); } - // Save current txt as last opened file + // Save current txt as last opened file and cache title for home screen APP_STATE.openEpubPath = txt->getPath(); + APP_STATE.openBookTitle = txt->getTitle(); + APP_STATE.openBookAuthor.clear(); // TXT files don't have author metadata APP_STATE.saveToFile(); // Trigger first update diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index ff9903d..ab1a8a6 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -83,8 +83,10 @@ void XtcReaderActivity::onEnter() { // Load saved progress loadProgress(); - // Save current XTC as last opened book and add to recent books + // Save current XTC as last opened book and cache title for home screen APP_STATE.openEpubPath = xtc->getPath(); + APP_STATE.openBookTitle = xtc->getTitle(); + APP_STATE.openBookAuthor.clear(); // XTC files don't have author metadata APP_STATE.saveToFile(); RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), "");