From b73ae7fe747c48dc46ae24768c2408b1202e29bf Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Sun, 21 Dec 2025 17:12:53 +1100 Subject: [PATCH] Paginate book list and avoid out of bounds rendering (#86) ## Summary * Paginate book list * Avoid out of bounds rendering of long book titles, truncate with ellipsis instead ## Additional Context * Should partially help with https://github.com/daveallie/crosspoint-reader/issues/75 as it was previously rendering a lot of content off screen, will need to test with a large directory --- .../EpubReaderChapterSelectionActivity.cpp | 2 + .../reader/FileSelectionActivity.cpp | 51 +++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index f4dfc373..9af556ba 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -5,8 +5,10 @@ #include "config.h" +namespace { constexpr int PAGE_ITEMS = 24; constexpr int SKIP_PAGE_MS = 700; +} // namespace void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) { auto* self = static_cast(param); diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp index 4389461f..d8aef3c7 100644 --- a/src/activities/reader/FileSelectionActivity.cpp +++ b/src/activities/reader/FileSelectionActivity.cpp @@ -5,6 +5,11 @@ #include "config.h" +namespace { +constexpr int PAGE_ITEMS = 23; +constexpr int SKIP_PAGE_MS = 700; +} // namespace + void sortFileList(std::vector& strs) { std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) { if (str1.back() == '/' && str2.back() != '/') return true; @@ -73,10 +78,12 @@ void FileSelectionActivity::onExit() { } void FileSelectionActivity::loop() { - const bool prevPressed = - inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); - const bool nextPressed = - inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); + 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)) { if (files.empty()) { @@ -101,11 +108,19 @@ void FileSelectionActivity::loop() { // At root level, go back home onGoHome(); } - } else if (prevPressed) { - selectorIndex = (selectorIndex + files.size() - 1) % files.size(); + } else if (prevReleased) { + if (skipPage) { + selectorIndex = ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + files.size()) % files.size(); + } else { + selectorIndex = (selectorIndex + files.size() - 1) % files.size(); + } updateRequired = true; - } else if (nextPressed) { - selectorIndex = (selectorIndex + 1) % files.size(); + } else if (nextReleased) { + if (skipPage) { + selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % files.size(); + } else { + selectorIndex = (selectorIndex + 1) % files.size(); + } updateRequired = true; } } @@ -126,21 +141,27 @@ void FileSelectionActivity::render() const { renderer.clearScreen(); const auto pageWidth = GfxRenderer::getScreenWidth(); - renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD); + renderer.drawCenteredText(READER_FONT_ID, 10, "Books", true, BOLD); // Help text renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30, "Press BACK for Home"); if (files.empty()) { renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found"); - } else { - // Draw selection - renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30); + renderer.displayBuffer(); + return; + } - for (size_t i = 0; i < files.size(); i++) { - const auto file = files[i]; - renderer.drawText(UI_FONT_ID, 20, 60 + i * 30, file.c_str(), i != selectorIndex); + 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 < files.size() && i < pageStartIndex + PAGE_ITEMS; i++) { + auto item = files[i]; + int itemWidth = renderer.getTextWidth(UI_FONT_ID, item.c_str()); + while (itemWidth > renderer.getScreenWidth() - 40 && item.length() > 8) { + item.replace(item.length() - 5, 5, "..."); + itemWidth = renderer.getTextWidth(UI_FONT_ID, item.c_str()); } + renderer.drawText(UI_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, item.c_str(), i != selectorIndex); } renderer.displayBuffer();