diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index 5a3edc4c..a37aaeea 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -377,6 +377,7 @@ void RecentBooksActivity::renderGridCell(int index, bool selected, int cellX, in if (!book.coverBmpPath.empty()) { const std::string thumbPath = gridThumbPath(book.coverBmpPath, tw, th); FsFile file; + bool thumbDrawn = false; if (Storage.openFileForRead("RBA", thumbPath, file)) { Bitmap bmp(file); if (bmp.parseHeaders() == BmpReaderError::Ok) { @@ -399,10 +400,19 @@ void RecentBooksActivity::renderGridCell(int index, bool selected, int cellX, in // shows through in the surrounding space. renderer.fillRect(cellX + offsetX, cellY + offsetY, rendW, rendH, false); renderer.drawBitmap1Bit(bmp, cellX + offsetX, cellY + offsetY, rendW, rendH); + thumbDrawn = true; } } file.close(); } + if (!thumbDrawn) { + // Thumbnail not yet generated — clear interior and show loading label + renderer.fillRect(cellX + 1, cellY + 1, tw - 2, th - 2, false); + const char* loadingText = tr(STR_LOADING); + const int textW = renderer.getTextWidth(SMALL_FONT_ID, loadingText); + const int textH = renderer.getLineHeight(SMALL_FONT_ID); + renderer.drawText(SMALL_FONT_ID, cellX + (tw - textW) / 2, cellY + (th - textH) / 2, loadingText, true); + } } else { // No cover — clear the whole interior so the placeholder looks clean. renderer.fillRect(cellX + 1, cellY + 1, tw - 2, th - 2, false); diff --git a/src/components/themes/lyra/Lyra3CoversTheme.cpp b/src/components/themes/lyra/Lyra3CoversTheme.cpp index 06f5f230..7fa25e8f 100644 --- a/src/components/themes/lyra/Lyra3CoversTheme.cpp +++ b/src/components/themes/lyra/Lyra3CoversTheme.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -32,10 +33,12 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con // Only load from SD on first render, then use stored buffer if (hasContinueReading) { if (!coverRendered) { + bool anyPending = false; for (int i = 0; i < std::min(static_cast(recentBooks.size()), Lyra3CoversMetrics::values.homeRecentBooksCount); i++) { std::string coverPath = recentBooks[i].coverBmpPath; bool hasCover = true; + bool tilePending = false; int tileX = Lyra3CoversMetrics::values.contentSidePadding + tileWidth * i; if (coverPath.empty()) { hasCover = false; @@ -62,6 +65,8 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con file.close(); } else { hasCover = false; + tilePending = true; // path exists but BMP not ready yet + anyPending = true; } } // Draw either way @@ -69,15 +74,29 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con coverHeight, true); if (!hasCover) { - // Render empty cover - renderer.fillRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection + (coverHeight / 3), - tileWidth - 2 * hPaddingInSelection, 2 * coverHeight / 3, true); - renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); + if (tilePending) { + // Cover is being generated — show a loading label centred in the tile + const char* loadingText = tr(STR_LOADING); + const int textW = renderer.getTextWidth(SMALL_FONT_ID, loadingText); + const int textH = renderer.getLineHeight(SMALL_FONT_ID); + renderer.drawText(SMALL_FONT_ID, + tileX + hPaddingInSelection + (tileWidth - 2 * hPaddingInSelection - textW) / 2, + tileY + hPaddingInSelection + (coverHeight - textH) / 2, loadingText, true); + } else { + // No cover at all — render empty cover placeholder + renderer.fillRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection + (coverHeight / 3), + tileWidth - 2 * hPaddingInSelection, 2 * coverHeight / 3, true); + renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); + } } } - coverBufferStored = storeCoverBuffer(); - coverRendered = coverBufferStored; // Only consider it rendered if we successfully stored the buffer + // Only cache the frame buffer once all tiles are definitively resolved. + // If any cover is still being generated we keep coverRendered=false so the next render will retry. + if (!anyPending) { + coverBufferStored = storeCoverBuffer(); + coverRendered = coverBufferStored; + } } for (int i = 0; i < std::min(static_cast(recentBooks.size()), Lyra3CoversMetrics::values.homeRecentBooksCount); diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 9ee2f037..c233137d 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -554,6 +554,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: if (!coverRendered) { std::string coverPath = book.coverBmpPath; bool hasCover = true; + bool coverPending = false; int tileX = LyraMetrics::values.contentSidePadding; if (coverPath.empty()) { hasCover = false; @@ -572,6 +573,9 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: hasCover = false; } file.close(); + } else { + hasCover = false; + coverPending = true; // path exists but BMP not ready yet } } @@ -579,14 +583,27 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth, coverHeight, true); if (!hasCover) { - // Render empty cover - renderer.fillRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection + (coverHeight / 3), coverWidth, - 2 * coverHeight / 3, true); - renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); + if (coverPending) { + // Cover is being generated — show a loading label centred in the cover area + const char* loadingText = tr(STR_LOADING); + const int textW = renderer.getTextWidth(SMALL_FONT_ID, loadingText); + const int textH = renderer.getLineHeight(SMALL_FONT_ID); + renderer.drawText(SMALL_FONT_ID, tileX + hPaddingInSelection + (coverWidth - textW) / 2, + tileY + hPaddingInSelection + (coverHeight - textH) / 2, loadingText, true); + } else { + // No cover at all — render empty cover placeholder + renderer.fillRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection + (coverHeight / 3), coverWidth, + 2 * coverHeight / 3, true); + renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); + } } - coverBufferStored = storeCoverBuffer(); - coverRendered = coverBufferStored; // Only consider it rendered if we successfully stored the buffer + // Only cache the frame buffer once the cover is definitively resolved (loaded or confirmed absent). + // If a cover is still being generated we keep coverRendered=false so the next render will retry. + if (!coverPending) { + coverBufferStored = storeCoverBuffer(); + coverRendered = coverBufferStored; + } } bool bookSelected = (selectorIndex == 0);